PC端微博Vue-Recyclerview元件的分析與資料採集

語言: CN / TW / HK

一般情況下,我們想採集微博的資料可以直接抓取wap端微博網頁即可,網頁結構非常簡單,爬取程式碼也非常簡單。

如果我們直接對PC端的微博網頁下手就會發生,介面引數非常複雜,改用selenium結果發現順序與網頁原始順序不一致,經認真研究發現原來是使用了vue-recyclerview元件。

下面以馬雲的微博為例,演示vue-recyclerview元件的特點,網址:http://weibo.com/mayun

開啟該網頁:

image-20220222174431679

可以看到每一條微博都是由一個class屬性為vue-recycle-scroller__item-view的div作為容器,同時每一個div都由css樣式表的transform屬性的translateY值進行相對偏移。在還沒有進行下翻時,translateY值按照從上往下的順序遞增。

上圖中的情況下有一個translateY值的-9999px,相當於從文件流中隱藏了。經過測量發現translateY的值決定了這條微博在整個文件中的位置,例如0px就是在頂部,549px就是距離頂部有549個畫素。

當我們下翻多條資料後:

image-20220222182139944

可以看到快取資料的容器剛開始是增加了,但是繼續下滑之後,div 數量不再變化,最大為15。顯然vue-recyclerview元件是迴圈利用了這個div容器來顯示資料,在滑動過程中,始終保持當前顯示的div在正確的位置。

不過這個元件對於selenium採集資料來說帶來了一定的困難,滑動到超過15條資料之前,文件流就已經不再按照順序擺放。

那麼我的解決思路是,在所有的vue-recycle-scroller__item-view的div中找出大於上一個節點的translateY的值的最小的一個節點作為下一個節點,從而正確的按順序獲取資料。

接下來,我們邊編碼邊講解思路。首先開啟谷歌遊覽器:

```python import os import winreg import time from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import WebDriverWait from selenium import webdriver

handle = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths") chrome_path = winreg.QueryValue(handle, "chrome.exe") command = f'"{chrome_path}" --remote-debugging-port=9222' os.popen(command) option = webdriver.ChromeOptions() option.add_experimental_option("debuggerAddress", "127.0.0.1:9222") browser = webdriver.Chrome(options=option) wait = WebDriverWait(browser, 10) ```

注意:如果你的谷歌遊覽器以前還沒有登入微博,要先手工登入一下。不然無法點選展開,無法檢視全部微博。

接下來,訪問馬雲的微博,並檢視每條顯示微博的div容器的translateY值:

python browser.get("http://weibo.com/mayun") scroller = wait.until(EC.presence_of_element_located( (By.XPATH, "//div[@id='scroller']"))) items = scroller.find_elements(By.CSS_SELECTOR, "div.vue-recycle-scroller__item-view") for item in items: matrix = item.value_of_css_property("transform") pos = int(matrix[matrix.rfind(" ")+1:-1]) print(pos)

0 566 1080 1672 -9999 -9999

然後我們嘗試解析第一條微博的資料。

對於時間欄位,我根據class是否以head-info_time開頭定位:

image-20220222185320538

對於微博內容,我根據header的鄰近兄弟的兒子的兒子進行定位,而展開按鈕則有一個expand的屬性:

image-20220222185734396

最終解析程式碼如下:

```python def remove_element(element): browser.execute_script(""" var element = arguments[0]; element.parentNode.removeChild(element); """, element)

def parse_data(item): # 遊覽器滾動到當前節點的可視區域 item.location_once_scrolled_into_view tag = item.find_element(By.XPATH, ".//article") time_tag = tag.find_element( By.XPATH, ".//header//a[contains(@class,'head-info_time')]") date = time_tag.get_attribute("title") print(date) text_tag = tag.find_element( By.XPATH, ".//header/following-sibling::div[1]/div/div") expands = text_tag.find_elements(By.XPATH, ".//span[@class='expand']") if expands: # 找到展開按鈕後,點選按鈕並刪除收起按鈕 expands[0].click()

time.sleep(0.1)

    collapse = wait.until(EC.presence_of_element_located(
        (By.XPATH, ".//span[@class='collapse']")))
    remove_element(collapse)
text = text_tag.text.strip()
html = text_tag.get_attribute("innerHTML").strip('\u200b \t\r\n')
return date, text, html

parse_data(items[0]) ```

結果:

('2020-10-17 15:53', '每年1400萬新生兒,是我們國家最寶貴的資源,也是我們國家最獨特的腦礦。這1400萬兒童接受什麼樣的教育,決定世界未來怎麼看待中國,決定了中國在世界上的地位,也決定了中國和世界的關係。這是今天教育巨大的責任和擔當所在。\n\n前天和100多位中小學校長一起交流了很多教育的想法,校長們的見解、困惑、思考,我想這都是我們一起在為未來教育做準備的一部分。今天的校長比任何時候都需要領導力。因為數字時代,世界的變化不亞於人類從農耕社會走向工業時代的變化,未來的教育必須改革。\n\n如果說老師是孩子人生的啟蒙教練,校長就是總教練,是教練們的教練。會教書不一定會當校長,校長考驗的是領導力、擔當、堅持和對未來的遠見。\n向校長們致敬,為校長們加油! ', '每年1400萬新生兒,是我們國家最寶貴的資源,也是我們國家最獨特的腦礦。這1400萬兒童接受什麼樣的教育,決定世界未來怎麼看待中國,決定了中國在世界上的地位,也決定了中國和世界的關係。這是今天教育巨大的責任和擔當所在。<br><br>前天和100多位中小學校長一起交流了很多教育的想法,校長們的見解、困惑、思考,我想這都是我們一起在為未來教育做準備的一部分。今天的校長比任何時候都需要領導力。因為數字時代,世界的變化不亞於人類從農耕社會走向工業時代的變化,未來的教育必須改革。<br><br>如果說老師是孩子人生的啟蒙教練,校長就是總教練,是教練們的教練。會教書不一定會當校長,校長考驗的是領導力、擔當、堅持和對未來的遠見。<br>向校長們致敬,為校長們加油! ')

再測試一下有連結的第二個微博:

python parse_data(items[1])

('2020-10-14 17:43', '謝謝那麼多網友利用自己的假期抽查螞蟻森林的作業,種樹需要我們一起認真!\n4年2.2億棵樹,這是5.5億人一起的努力。一個人做所有的事,不算公益;很多人做一點點才是公益!螞蟻森林ANTFOREST的優酷視訊', '謝謝那麼多網友利用自己的假期抽查螞蟻森林的作業,種樹需要我們一起認真!<br>4年2.2億棵樹,這是5.5億人一起的努力。一個人做所有的事,不算公益;很多人做一點點才是公益!<a target="_blank" href="http://t.cn/A6borgmv"><img class="icon-link" src="http://h5.sinaimg.cn/upload/2015/09/25/3/timeline_card_small_video_default.png">螞蟻森林ANTFOREST的優酷視訊</a>')

再獲取一下馬雲微博的總條數:

python all_num_tag = browser.find_element( By.XPATH, "//div[@class='container']/div/div[starts-with(text(),'全部微博')]") num = int(all_num_tag.text[5:-1]) all_num

197

然後開始採集:

```python last_pos = -1 data = []

for _ in range(all_num): tmp = [] items = scroller.find_elements(By.CSS_SELECTOR, "div.vue-recycle-scroller__item-view") for item in items: matrix = item.value_of_css_property("transform") pos = int(matrix[matrix.rfind(" ")+1:-1]) if pos <= last_pos: continue tmp.append((pos, item)) last_pos, item = min(tmp, key=lambda x: x[0]) date, text, html = parse_data(item) time.sleep(0.1) data.append((date, text, html)) data ```

執行過程中:請新增圖片描述

經過一分鐘的時間終於採集完畢,現在我們將其轉換為pandas物件:

```python import pandas as pd

df = pd.DataFrame(data, columns=["釋出時間", "內容", "HTML"]) df ```

image-20220222211617293

下面整理一下完整程式碼:

```python import pandas as pd import os import winreg import time from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import WebDriverWait from selenium import webdriver

handle = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths") chrome_path = winreg.QueryValue(handle, "chrome.exe") command = f'"{chrome_path}" --remote-debugging-port=9222' os.popen(command) option = webdriver.ChromeOptions() option.add_experimental_option("debuggerAddress", "127.0.0.1:9222") browser = webdriver.Chrome(options=option) wait = WebDriverWait(browser, 10)

def remove_element(element): browser.execute_script(""" var element = arguments[0]; element.parentNode.removeChild(element); """, element)

def parse_data(item): # 遊覽器滾動到當前節點的可視區域 item.location_once_scrolled_into_view tag = item.find_element(By.XPATH, ".//article") time_tag = tag.find_element( By.XPATH, ".//header//a[contains(@class,'head-info_time')]") date = time_tag.get_attribute("title") text_tag = tag.find_element( By.XPATH, ".//header/following-sibling::div[1]/div/div") expands = text_tag.find_elements(By.XPATH, ".//span[@class='expand']") if expands: # 找到展開按鈕後,點選按鈕並刪除收起按鈕 expands[0].click() collapse = wait.until(EC.presence_of_element_located( (By.XPATH, ".//span[@class='collapse']"))) remove_element(collapse) text = text_tag.text.strip() html = text_tag.get_attribute("innerHTML").strip('\u200b \t\r\n') return date, text, html

browser.get("http://weibo.com/mayun") scroller = wait.until(EC.presence_of_element_located( (By.XPATH, "//div[@id='scroller']"))) all_num_tag = browser.find_element( By.XPATH, "//div[@class='container']/div/div[starts-with(text(),'全部微博')]") all_num = int(all_num_tag.text[5:-1]) last_pos, data = -1, []

for i in range(all_num): tmp, items = [], None while True: items = scroller.find_elements(By.CSS_SELECTOR, "div.vue-recycle-scroller__item-view") for item in items: matrix = item.value_of_css_property("transform") pos = int(matrix[matrix.rfind(" ")+1:-1]) if pos <= last_pos: continue tmp.append((pos, item)) if items: break time.sleep(0.2) last_pos, item = min(tmp, key=lambda x: x[0]) date, text, html = parse_data(item) print(i, date) time.sleep(0.1) data.append((date, text, html))

df = pd.DataFrame(data, columns=["釋出時間", "內容", "HTML"]) df.to_excel("馬雲微博.xlsx", index=False) ```

注意:本文主要目的是研究對vue-recyclerview元件的資料採集,真需要大規模爬取微博資料時,建議使用requests爬取WAP端微博。

可參考:http://buyixiao.github.io/blog/weibo-super-spider.html