小言_互联网的博客

爬虫系列(2):暴力爬虫——基于selenium和xpath定位方法爬取某个话题下的微博内容

381人阅读  评论(0)

为什么这期要叫暴力爬虫呢?因为笔者认为基于selenium方法非常的简单粗暴!!理由有两点:

1.在selenium方法下,我们可以通过调用浏览器驱动来实现模拟鼠标点击、滑轮下滑以及输入文本等操作,就像真正的用户正在操作浏览器一样(如此一来便可以解决某些需要用户登录才能获取界面的网站),而且在访问网站层面,它的安全性是高于requests方法的,因为它不需要构造一个虚拟请求,所有的操作都是真实发生的。

2.selenium获取网页信息的方法是基于网页的elements而不是network。网页的编排规则一般是:network 下显示了服务器响应给浏览器的文件,这些文件可能包含html、json等格式,浏览器拿到这些文件后,组装成 elements,显示出来。简单理解为,elements是封装得最好的信息,我们在网页上看得到的,elements中都有,即“所见即可得”。在这种情况下,获取到的数据文本就不需要再经过复杂的解析。

目前selenium只支持通过Firefox和Chrome两款浏览器进行操作,本文以Firefox为例进行介绍。

安装Firefox驱动

根据操作系统下载Firefox驱动的压缩包,Firefox驱动的中文下载网站:https://liushilive.github.io/github_selenium_drivers/md/Firefox.html

下载好压缩包后,解压得到geckodriver.exe文件,将该文件安装到Python的环境文件夹中site-packages,也可以单独对该文件配置环境变量,具体的配置方法请参考这篇文章:https://blog.csdn.net/eerty2588/article/details/107041770

导入selenium的相关方法

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import selenium.webdriver.support.ui as ui
from selenium.webdriver.common.action_chains import ActionChains  ##引入ActionChains鼠标操作类

设定目标内容,分析网站结构***

需求场景:因为今年是建dang百年,笔者想去dang的出生地‘’嘉兴南湖‘’旅游,所以想先到微博上看看相关的评价。

我们以mobile端的微博为例(网站地址:https://m.weibo.cn/)对#嘉兴南湖#下的内容进行爬取。首先手工打开网页模拟一遍操作流程

1.打开网页,得到首页信息
微博mobile版的首页如下,网页最上端即搜索栏

2.用鼠标点击搜索栏,输入搜索内容
然而我们发现,这个搜索栏并不是直接可以输入文本的,我们需要用鼠标点一下,进入到如下图所示的这一个输入界面,在这个输入框中,可以直接敲键盘输入‘#嘉兴南湖#’的内容,之后再敲一下键盘上的回车

3.分析话题下的用户内容***
敲回车之后得到的页面是这样的,微博默认以“综合”评价的方式对用户数据进行排列,但我们想以时间的顺序有先有后地来浏览用户评论内容,因此点击“实时”按钮

得到在以时间先后顺序排列下的用户内容呈现界面,接下来就是去定位用户的评论信息。我们发现:一个用户的评论,由以下几个部分组成:
①用户名
②发布时间
③发布工具(手机型号或者网页类型)
④评论内容
⑤所附图片
⑥转发数
⑦评论数
⑧点赞数
由于图片无法直接获取显示到Python的console中,因此我们决定爬取用户名、发布时间、发布工具、评论内容、转发数、评论数和点赞数这几个方面的信息。

4.翻页获取新内容***
确定了要获取内容后,并不是万事大吉了!因为我们不确定当前页面是否包含了所有的评论信息,所以先把页面滑轮拉到页面底端,果然出现了一个加载中…,这意味着在微博动态页面加载的技术框架下,当前页面并没有包含所有评论内容,需要我们不断地下滑滑轮到页面底端,获取到新的微博内容。最后,在下翻了N多次之后,终于找到了最早的一篇微博,发表在2010年的10月8日,此时即使把滑轮滑到底部,微博也不会再加载出新的页面。这时候,我们所处的页面已经包含了#嘉兴南湖#话题之下的所有内容!!! 接下来便总结一下我们所需要做的工作。

总结所需要做的工作

1.需要模拟登陆网页,可以通过selenium库中的webdriver方法来构造driver变量实现。

driver=webdriver.Firefox() 
driver.get('https://m.weibo.cn/')

2.需要实现模拟鼠标分别点击刚进去微博首页的搜索栏、以及在#嘉兴南湖#话题之下的实时按钮,这一步通过调用click()方法实现。具体步骤是:①通过selenium的find_elemens_by_xpath/id/css/class_name等方法定位搜索栏和实时按钮所属地址;②通过click()方法点击

driver.find_element_by_class_name('m-search').click()  #模拟鼠标点击微博首页搜索栏
driver.find_element_by_xpath('/html/body/div[1]/div[1]/div[1]/div[3]/div[2]/div[1]/div/div/div/ul/li[2]/span').click() #模拟鼠标点击实时按钮

3.需要实现模拟键盘输入内容并敲回车键,这一步通过send_keys()方法实现,输入文本的步骤就是找到输入框地址,然后send_keys("【文本内容】"),模拟回车键则是send_keys(Keys.ENTER)。

driver.find_element_by_xpath('/html/body/div/div[1]/div[1]/div[1]/div/div/div[2]/form/input').send_keys('#嘉兴南湖#')  #找到输入框地址并输入#嘉兴南湖#
driver.find_element_by_xpath('/html/body/div/div[1]/div[1]/div[1]/div/div/div[2]/form/input').send_keys(Keys.ENTER)  #模仿敲下回车键

4.需要滑动滑轮至页面底端
这个没什么好说的,直接上执行代码,执行一次,滑轮自动滑到页面底端,等微博内容加载完毕之后,再执行一次,重复上述操作。

driver.execute_script("window.scrollTo(0,document.body.scrollHeight);")

5.需要定位每一篇微博内容构成元素的地址:这里为了方便,我们皆以xpath的方法进行定位,这里推荐一个定位xPath的神器——xPath Finder,可从Firefox插件市场下载并进行拓展,用了这个插件后,用鼠标点击网页特定的元素,就会自动地在页面左下角显示该元素的xPath,可大大提高工作效率。


在定位好各元素的xPath之后,就可以分别获取并将其赋值变量。注意,这里用的是find_elements_by_xpath方法而不是find_element_by_xpath,因为笔者发现find_element_by_xpath通常无法获取到正确内容,正因为如此,获取到的目标是一个列表型数据,需要在后面加上一个[0].text以得到内容。

##获取第一篇微博中的各个元素信息##          
headers=driver.find_elements_by_xpath('/html/body/div[1]/div[1]/div[1]/div[4]/div/div/div/header/div/div/a/h3')[0].text
date=driver.find_elements_by_xpath('/html/body/div[1]/div[1]/div[1]/div[4]/div/div/div/header/div/div/h4/span[1]')[0].text
phone=driver.find_elements_by_xpath('/html/body/div[1]/div[1]/div[1]/div[4]/div/div/div/header/div/div/h4/span[2]')[0].text
content=driver.find_elements_by_xpath('/html/body/div[1]/div[1]/div[1]/div[4]/div/div/div/article/div/div[1]')[0].text
zhuanfa=driver.find_elements_by_xpath('/html/body/div[1]/div[1]/div[1]/div[4]/div/div/div/footer/div[1]/h4')[0].text
pinglun=driver.find_elements_by_xpath('/html/body/div[1]/div[1]/div[1]/div[4]/div/div/div/footer/div[2]/h4')[0].text
dianzan=driver.find_elements_by_xpath('/html/body/div[1]/div[1]/div[1]/div[4]/div/div/div/footer/div[3]/h4')[0].text

重难点

1.如何判断滑轮已经翻到页面底部?
对于这个问题,笔者的解决办法是去拿执行翻页操作之前微博内容对应的class_name或者id_name数量和执行完翻页操作之后的数量做比较,如果在执行完翻页之后,发现后者数量大于前者,说明这一个翻页是有效的,如果发现二者数量依然相等,说明翻页已经到了页面最低端,不需要再进行翻页了,为了方便理解,算法规则如下图所示。在最后的实现代码中,我是以每一个微博内容所对应的class_name"weibo-text" 来作为变量进行计算的。

2.如何设计爬取微博内容的循环结构
我们观察第一篇微博内容的xpath是
’/html/body/div[1]/div[1]/div[1]/div[4]/div/div/div/article/div/div[1]’
用xPath_Finder再点一下第二篇微博内容,xpath变为了
’/html/body/div[1]/div[1]/div[1]/div[5]/div/div/div/article/div/div[1]’
那么便可以认为,决定微博内容顺序的标签在第四个div[]中,并且顺序排列是以4为初始数值,之后每一篇微博数值递增1的方法去实现,那么在最终的程序实现中,只需要构建类似一个for i in range(4,xxx):div[+str(i)+] 的循环便可以实现。对于range的上限,正常的思路是通过计算页面中每一篇微博所对应的class_name数量确定总数,但是笔者实践下来发现这个数目其实并不太对,因此我解决的思路有点笨:通过判定执行出错的次数来判定微博是否已经提取完毕,如果提取完毕后,依然执行driver.find_elements_by_xpath,这时候程序就会报错,每报错一次,通过try except 的方法执行累加计算,累计报错次数就+1,设置一个小的阈值(比如5),当报错达到5次之后,就退出循环结束程序。这样一来初始上限可以大胆地设置为任意一个超级大的数,比如1000000。

项目代码实现

完整程序和相关注释如下

from selenium import webdriver
import time
import pandas as pd
from selenium.webdriver.common.keys import Keys
import selenium.webdriver.support.ui as ui
from selenium.webdriver.common.action_chains import ActionChains  ##引入ActionChains鼠标操作类

def get_comments_from_weibo():
    driver=webdriver.Firefox()   #调用Firefox浏览器
    #driver.set_headless()    #无页面显示操作,为了方便观察程序运行状态,这个可以不加
    driver.get('https://m.weibo.cn/')   #访问微博页面
    time.sleep(2)   #设置一个休息时间,模拟人类正常的访问操作,也给页面留出足够的加载时间,下同
    driver.find_element_by_class_name('m-search').click()    #点击搜索栏
    time.sleep(2)
    driver.find_element_by_xpath('/html/body/div/div[1]/div[1]/div[1]/div/div/div[2]/form/input').send_keys('#嘉兴南湖#')  #在输入框中输入#嘉兴南湖#
    time.sleep(1)
    driver.find_element_by_xpath('/html/body/div/div[1]/div[1]/div[1]/div/div/div[2]/form/input').send_keys(Keys.ENTER)  #模仿回车键输入
    time.sleep(3)
    driver.find_element_by_xpath('/html/body/div[1]/div[1]/div[1]/div[3]/div[2]/div[1]/div/div/div/ul/li[2]/span').click()     #点击实时按钮
    time.sleep(3)
    before_turning_number=0
    global weibo_df
    weibo_df=pd.DataFrame(columns=['headers','date','phone','content','zhuanfa','pinglun','dianzan'])  #设置一个用户存储获取内容的df
    after_turning_number=len(driver.find_elements_by_class_name('weibo-text')) 
    row_number=0
    error_number=0   #设置初始错误次数为0
    ##翻页到底程序段##
    while after_turning_number>before_turning_number:
          before_turning_number=after_turning_number
          driver.execute_script("window.scrollTo(0,document.body.scrollHeight);")   #执行下滑滑轮到底端操作
          time.sleep(3)  #执行翻页后的等待时间可以结合自身的网络情况适当延长
          after_turning_number=len(driver.find_elements_by_class_name('weibo-text'))
    else:
        print('翻页结束')
        time.sleep(3)
    ##抽取用户信息、用户评论内容程序段##
    for i in range(4,1000000):
      if error_number<5:  #当错误次数小于5时,执行获取微博内容的命令
        try:
          headers=driver.find_elements_by_xpath('/html/body/div[1]/div[1]/div[1]/div['+str(i)+']/div/div/div/header/div/div/a/h3')[0].text
          date=driver.find_elements_by_xpath('/html/body/div[1]/div[1]/div[1]/div['+str(i)+']/div/div/div/header/div/div/h4/span[1]')[0].text
          phone=driver.find_elements_by_xpath('/html/body/div[1]/div[1]/div[1]/div['+str(i)+']/div/div/div/header/div/div/h4/span[2]')[0].text
          content=driver.find_elements_by_xpath('/html/body/div[1]/div[1]/div[1]/div['+str(i)+']/div/div/div/article/div/div[1]')[0].text
          zhuanfa=driver.find_elements_by_xpath('/html/body/div[1]/div[1]/div[1]/div['+str(i)+']/div/div/div/footer/div[1]/h4')[0].text
          pinglun=driver.find_elements_by_xpath('/html/body/div[1]/div[1]/div[1]/div['+str(i)+']/div/div/div/footer/div[2]/h4')[0].text
          dianzan=driver.find_elements_by_xpath('/html/body/div[1]/div[1]/div[1]/div['+str(i)+']/div/div/div/footer/div[3]/h4')[0].text
          weibo_df.loc[row_number]=[headers,date,phone,content,zhuanfa,pinglun,dianzan]
          row_number=row_number+1
        except:
            error_number=error_number+1  #错误次数加1
            continue  #继续执行循环
      else:
           break
get_comments_from_weibo()

得到的结果如下


根据以上所得到的的内容便可以进行进一步的词频统计和语义关联分析等文本分析,在此就不再赘述。


转载:https://blog.csdn.net/weixin_42466310/article/details/115213945
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场