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