項目需導入 QQ 聯系人,但騰訊未開放官方 API。開源協議庫 LumaQQ 登錄不穩定,維護成本高。發現 QQ 郵箱(mail.qq.com) 可在瀏覽器查看聯系人,決定採用「程序模擬瀏覽器登錄郵箱 → 解析聯系人頁面」方案。
uin(QQ號)、p(加密密碼)、verifycode(驗證碼)、sid、ts 等。sid、qqmail_sid 等)。POST /cgi-bin/login?sid=0,2,zh_CN HTTP/1.1
Host: m126.mail.qq.com
User-Agent: Mozilla/5.0 ...
Content-Type: application/x-www-form-urlencoded
Cookie: verifysession=...; pvid=...; r_cookie=...
sid=0%2C2%2Czh_CN&firstlogin=false&starttime=1251436516205
&f=html&p=UyoLX7JYspcnU9wCUK7qBA2Xjwo%2FO52FldnJnRABCnNLXI1ndwR8%2BQaepkT%2FIcKHxMshWv4SjrxgvRtA%2B1eHAookTXzcia%2FhP5QrRlGyNoqzVCi1X%2FsJwb4SmjPZRwV%2B7WeskUwClUfeNv%2BmCq0ygoNzi9UnIVeI7lYzgqXnxZE%3D
&delegate_url=&s=&ts=1251436494&uin=XXXXXXXX&aliastype=%40qq.com&pp=000000000&verifycode=qmpb
關鍵字段說明:
p:RSA 加密後的密碼,格式為 密碼\nTS\n 經公鑰加密再 Base64。ts:時間戳,從登錄頁面獲取。verifycode:驗證碼輸入值。uin:QQ 號。Set-Cookie: sid=6449e26f4adc402ac5c80e08bec3ca6c; Domain=mail.qq.com; Path=/
Set-Cookie: username=XXXXXXXX; Domain=mail.qq.com; Path=/
Set-Cookie: qqmail_sid=6449e26f4adc402ac5c80e08bec3ca6c; Domain=qq.com; Path=/
Set-Cookie: qqmail_key=1_de7d3652ab10a1616175bf2aab152fb7010b0d06020602060309; Domain=qq.com; Path=/
後續請求必須攜帶上述 Cookie(特別是 sid、qqmail_sid、qqmail_key)。
| 難點 | 解決方案 |
|---|---|
| 密碼 RSA 加密 | 從登錄頁面 JS (auth.js) 提取公鑰模數/指數,Java 端用 RSAPublicKeySpec 重現加密邏輯,或直接調用 JS 引擎 (Rhino/Nashorn) 執行原 JS 代碼。 |
| 驗證碼識別 | 人工輸入 / 打碼平台 / 簡單圖片識別 (Tesseract) / 避開需驗證碼的登錄入口(如二維碼登錄)。 |
| 動態參數 (ts, starttime) | 每次登錄前先 GET 登錄頁面解析隱藏字段,實時構造表單。 |
| Cookie 管理 | 使用 HttpClient (Apache HttpComponents) 自動維護 CookieStore,支持重定向。 |
| 反爬蟲風控 | 控制請求頻率、模擬真實 User-Agent、使用代理 IP 池、處理滑塊/設備指紋驗證。 |
public class QQMailContactImporter {
private CloseableHttpClient httpClient;
private String qq, pwd;
public QQMailContactImporter(String qq, String pwd) {
this.qq = qq; this.pwd = pwd;
this.httpClient = HttpClients.custom()
.setDefaultCookieStore(new BasicCookieStore())
.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...")
.build();
}
public List importContacts() throws Exception {
// 1. 獲取登錄頁面,解析公鑰、ts、verifycode URL
LoginPageInfo info = fetchLoginPage();
// 2. 獲取驗證碼圖片(可選)
// 3. RSA 加密密碼
String encryptedPwd = RSAEncrypt.encrypt(pwd + "\n" + info.ts + "\n", info.publicKey);
// 4. 提交登錄表單
boolean loginOk = submitLogin(info, encryptedPwd, "verifycode_input");
if (!loginOk) throw new RuntimeException("登錄失敗");
// 5. 訪問聯系人列表頁面
String contactsHtml = fetchContactsPage();
// 6. 解析聯系人數據
return parseContacts(contactsHtml);
}
// ... 省略具體方法實現
}
下篇將詳細講解:RSA 加密 Java 實現、聯系人列表頁面解析策略、完整可運行 Demo 代碼、異常處理與重試機制。