[Python]消失在QQ空間里的青春
QQ空間,這個曾經陪我們從童年到少年再到成年,從2G時代再到如今的4G末,占據了我們太多的青春回憶,如今好友空間動態更新的不在像從前那樣頻繁。依稀記得當年的好友買賣,搶車位再或者情侶空間,現在想想那時候真的很幼稚,那就是我們傻逼的童年,什么互踩,火星文,跑堂見證了我們無憂無慮的童年。
有時候看看QQ推送的"那年今日",看到自己好幾年前發的動態,說的傻話,自己都怕了自己。有時候看到好朋友幾年前的動態,不由笑罵道,這孫子,怎么這么2! 即使現在不怎么用QQ了,有時候看看曾經發的說說還有空間的留言。即使讓我再尷尬也不舍得刪,因為那都是青春滿滿的回憶。
空間留言上千條,說說也比較多,一頁一頁的翻比較麻煩。索性就把這些數據都下載到本地。同理我們也可以導出全部聯系人的說說和留言板。
Selenium
由于訪問好友留言板需要登錄,為了方便起見我們使用Web應用程序測試的Selenium工具。該工具可以用于單元測試,集成測試,系統測試等等。它可以像真正的用戶一樣去操作瀏覽器等,支持Mozilla Firefox、Google Chrome、Safari、Opera、IE等等瀏覽器。
使用這個工具之前我們需要安裝selenium庫和下載相應瀏覽器的驅動。然后通過分析QQ空間登錄界面我們發現默認是掃碼登錄,因此需要切換成賬號密碼登錄。
通過分析html標簽屬性,我們發現 id="switcher_plogin",是個切換登錄的全局唯一屬性。同理我們再需要找到賬號、密碼輸入框和點擊登錄的元素就可以用selenium模擬登錄了
登錄部分代碼如下:
- from selenium import webdriver
- driver = webdriver.Chrome()
- # 獲取谷歌瀏覽器驅動
- driver = webdriver.Chrome()
- # 登錄網站
- driver.get('https://i.qq.com')
- # 選擇賬號密碼登錄
- driver.switch_to_frame('login_frame')
- # 點擊輸入框獲取輸入
- driver.find_element_by_id('switcher_plogin').click()
- # 輸入賬號
- driver.find_element_by_id('u').send_keys('你的qq號')
- # 輸入密碼
- driver.find_element_by_id('p').send_keys('qq密碼')
- # 點擊登錄
- driver.find_element_by_id('login_button').click()
工作前的參數準備
通過查看開發者工具中的請求我們發現,登錄之后每次請求除了攜帶必要的參數以外,還攜帶了登錄獲取的token和g_tk。token我們可以從網頁源代碼中直接獲取,但是g_tk在源代碼中沒有,根據以往經驗第一步只能從js中查看,果然發現了一段加密代碼,再結合上下文發現是從cookie中取出“p_skey”的值再經過一系列操作就是g_tk的值了。因為我們需要先獲取cookie,然后再通過cookie獲取g_tk。
部分js加密邏輯代碼
- if (e) {
- if (e.host && e.host.indexOf("qzone.qq.com") > 0) {
- try {
- t = parent.QZFL.cookie.get("p_skey")
- } catch(e) {
- t = QZFL.cookie.get("p_skey")
- }
- }
- ............
- }
- "g_tk=" + QZFL.pluginsDefine.getACSRFToken(t)
- QZFL.pluginsDefine.getACSRFToken._DJB = function(e) {
- var t = 5381;
- for (var n = 0,
- r = e.length; n < r; ++n) {
- t += (t << 5) + e.charCodeAt(n)
- }
- return t & 2147483647
- };
獲取token && cookie && g_tk代碼
- """
- 獲取g_tk的值
- """
- def get_g_tk(cookie):
- hashes = 5381
- for letter in cookie['p_skey']:
- hashes += (hashes << 5) + ord(letter)
- return hashes & 0x7fffffff
- # 獲取登錄之后的cookie信息
- cookie = {}
- for elem in driver.get_cookies():
- cookie[elem['name']] = elem['value']
- # 獲取g_tk
- g_tk = get_g_tk(cookie)
- # 利用xpath獲取登錄之后的網頁源代碼
- html = driver.page_source
- xpath = r'window\.g_qzonetoken = \(function\(\)\{ try\{return "(.*?)";}'
- # 通過xpath 獲得登錄后的token
- token = re.compile(xpath).findall(html)[0]
開始搞事
破解了一個簡單的反爬蟲,下面就可以編寫正式的爬蟲代碼了,首先確定我們目標url、通過瀏覽器分析響應的json對象、編寫headers
因為每次請求都需要攜帶登錄信息,為了方便我們用到了session類,其次觀察相應我們發現返回的response有無用的字符,因此需要進行截取
- headers = {
- 'authority': 'user.qzone.qq.com',
- 'method': 'GET',
- 'scheme': 'https',
- 'accept-language': 'zh-CN,zh;q=0.9',
- 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36',
- }
- def get_resp(cookie, g_tk, token, page):
- session = requests.session()
- # 將cookie字典轉換成RequestsCookieJar
- c = requests.utils.cookiejar_from_dict(cookie)
- # 將headers 放入session
- session.headers = headers
- # RequestsCookieJar復制給session
- session.cookies = c
- # 訪問留言板的url
- url = f'https://user.qzone.qq.com/proxy/domain/m.qzone.qq.com/cgi-bin/new/get_msgb?uin={登陸的qq}&hostUin={要查詢留言內容的QQ號}&start={page}&num=10&g_tk={g_tk}&qzonetoken={token}'
- print(url)
- response = session.get(url)
- # 截取無用的字符
- resp_text = response.text[10: -3]
- # 轉為json
- resp_json = json.loads(resp_text)
- return resp_json
上面的方法,只是獲得了某一頁的接口相應,我們通過json獲取留言總數,再除以每頁的條數,就可以知道總頁數了。然后再遍歷去取每頁的數據,為了方便查看將數據保存在csv文件中,另外將留言內容保存在txt文件中,生成詞云。
- def get_zone_xx(cookie, g_tk, token, page=0):
- # 初始化請求為了取總條數
- resp_json = get_resp(cookie, g_tk, token, page)
- # 總條數
- total = resp_json['data']['total']
- print(f'共{total}條留言信息')
- # 總頁數
- size = int(total/10 + 1)
- # 已經讀取的信息條數
- use_page = 0
- # 保存每條數據信息,生成csv文件用
- content_arr = []
- for i in range(0, size):
- # 請求每一頁的內容
- resp_json = get_resp(cookie, g_tk, token, i)
- # 當條數大于或等于總條數 跳出循環
- if use_page >= total:
- break
- # 從每頁數據中取出需要的字段值
- for comment in resp_json['data']['commentList']:
- use_page += 1
- print(f'當前正在讀取第{use_page}條')
- page_json = []
- # 留言日期
- page_json.append(comment['pubtime'])
- # 昵稱
- page_json.append(comment['nickname'])
- # 內容
- content = replace_html(comment['htmlContent'])
- # 將內容寫入文本 生成詞云用
- with open('zone_text111.txt', 'a') as f:
- f.write(content)
- page_json.append(content)
- content_arr.append(page_json)
生成csv文件
- # 將總數據轉化為data frame再輸出
- df = pd.DataFrame(data=content_arr,
- columns=['留言日期', '昵稱', '留言內容'])
- df.to_csv('QQ_ZONE.csv', index=False, encoding='utf-8_sig')
- print('已保存為csv文件.')
運行上面代碼生成csv文件部分內容如下
生成詞云(wordcloud)代碼如下
- from wordcloud import WordCloud
- import matplotlib.pyplot as plt
- with open('zone_text.txt','r') as f:
- mytext = f.read()
- font = r'C:\Windows\Fonts\simfang.ttf'
- wc = WordCloud(collocations=False, font_path=font, width=1400, height=1400, margin=2).generate(mytext)
- plt.imshow(wc)
- plt.axis("off")
- plt.show()
- plt.show()
運行結果如下:
寫在最后
上面的代碼并沒有太復雜,也許是觸景生情,也許是對現在朋友圈各種亂七八糟的信息產生了抵觸,所以試著去回憶青春的那些往事。
朋友圈和空間并不能去衡量一個人是是否成熟,但是對于大部分90后來說,空間真的是承載了太多純真的回憶。不忘初心,砥礪前行!!!