python学习Python学习资料python

手把手系列:用Python3+PyQt5做一个有界面的小爬虫(二

2018-09-19  本文已影响593人  杳杳靈鳯

写出你的第一个小爬虫

在上一篇文章中对Python有了一定的基础学习后,我们现在要开始对网页进行爬取啦。

这次我们要爬取的网站是中国移动集团的运维案例文章和评论内容。这篇文章中将会涉及到GET请求和POST请求,以及BeautifulSoup的使用。

扩展模块的安装

要让python可以对网页发起请求,那就需要用到requests之类的包。我们可以用命令行来下载安装,打开cmd,直接输入命令就会自动下载安装,非常的方便。

pip install requests

既然用到了pip,那就顺便解释一下这个东东。

pip 是 Python 著名的包管理工具,在 Python 开发中必不可少。一般来说当你安装完python后,pip也会自动安装完毕,可以直接享用,十分鲜美。

附上一些常用的pip命令

# 查看pip信息
pip –version

#升级pip
pip install -U pip

# 查看已经安装的包
pip list

# 安装包
Pip install PackageName

# 卸载已经安装好的包
Pip uninstall PackageName

以上这四个命令非常的实用,我在做这个爬虫期间有多次使用到。

在安装完requests包后,还需要再安装一个神器BeautifulSoup,配合lxml库,这是最近非常流行的两个库,这个是用来解析网页的,并提供定位内容的便捷接口,API非常人性化,支持CSS选择器、Python标准库中的HTML解析器,也支持 lxml 的 XML解析器

语法使用类似于XPath。通过官方的文档的阅读,很容易上手。

安装方式:

pip install beautifulsoup4
pip install lxml

不报错即为安装成功

BeautifulSoup官方文档传送门

安装完上面三个包后,就开始制作我们的第一个小爬虫吧

我们先来分析一下我们这次要爬取数据的网站数据。可以使用chrome浏览器自带的工具来抓包,按F12,选择Network,就可以看到所有的请求内容了。当然也可以用Fiddler这个优秀的工具来抓包,还能对请求进行截获并修改参数,大家有时间的话可以去玩一玩。这儿因为方便,我就采用了chrome浏览器自带的来进行分析了。

根据这个请求,我们能看出这个是一个GET请求。我们将headers里的内容绑定到类的属性里,接着绑定一个请求的地址。

import requests  #导入requests 模块
from bs4 import BeautifulSoup  #导入BeautifulSoup 模块

# 爬取文章案例编号和当前周期有效阅读数
class BeautifulGetCaseSN():
    def __init__(self):  #类的初始化操作
        #头部信息
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.221 Safari/537.36 SE 2.X MetaSr 1.0',
            'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
            'Accept-Language':'zh-CN,zh;q=0.8',
            'Connection':'keep-alive',
            'Cookie':'JSESSIONID=48A82C60BE65ED14C5978297C03AF776; PHPSESSID=ST-10-QybrBt31XVfPBqKAT2jr'
        }  
        #要访问的网页地址
        self.web_url = 'http://net.chinamobile.com/cmccnkms-case/web/casesPortal/getCaseInfor.action?caseId=75058'  

接下来我们再来分析一下要爬取的内容,下图中的三个框是我们要爬取的目标内容,分别是标题,当前周期有效阅读数,案例编号。

分别右击审查元素,分析一下HTML的结构,以方便用BeautifulSoup来解析。

看了一遍这个网页的HTML,发现写这个网站的人真的是随意发挥,哈哈哈,都是直接用标签对的,标签class属性或者id属性几乎都没有,还好我们现在有了神器Beautifulsoup再手,根本不用愁无从下手这种的事儿。

通过分析,我们发现标题和当前有效周期的父级标签都是<td width=“78%”>,而且我搜索了下,发现width=“78%”属性只有这两个标签有。那么好办了,利用Beautifulsoup能对CSS属性解析的特性,我们就从这个属性下手。接着通过对案例编号的分析,我们发现这个<td class=“txleft”>标签有一个class属性,那就根据这个属性进行获取。

def get_data(self):
    print('开始文章基础信息GET请求')
    r = requests.get(self.web_url, headers=self.headers)
    all_soup = BeautifulSoup(r.text, 'lxml')

    caseSn = all_soup.find_all('td','txleft') #案例编号抓取
    print(caseSn[2].text)

    all_a = all_soup.find_all('td',width='78%')
    title = all_a[0].find('h4').text #标题抓取
    print(title)

    read_num = all_a[1].find_all('span')
    print(read_num[3].text) #有效阅读数抓取

解释一下这段代码:

r = requests.get(self.web_url, headers=self.headers)
all_soup = BeautifulSoup(r.text, 'lxml')

对网站进行GET请求,GET请求需要的参数有请求地址和头部信息,然后将获取的文本放入BeautifulSoup进行解析。这儿顺带说一句,python语言真的是人生苦短啊,请求网址,解析网页2句话就能完成,简洁的不得了,当然这个BeautifulSoup我为了逻辑清楚分开写了,不然也是能用一句代码来完成的。

caseSn = all_soup.find_all('td','txleft') #案例编号抓取
print(caseSn[2].text)

解析获取所有class类名为txleft的td标签,然后发现我们需要的案例编号是在第三个tag中,获取这个tag的文本内容。

all_a = all_soup.find_all('td',width='78%')
title = all_a[0].find('h4').text #标题抓取
print(title)

read_num = all_a[1].find_all('span')
print(read_num[3].text) #有效阅读数抓取

通过打断点的方式,我们可以看到解析获取所有宽度属性为78%的td标签,一共有2个tag集,再在第1个标签集中解析获取为h4的标签,这个全文只存在唯一的一个,所以就获取到了我们所需要的文章标题。有效阅读数获取同理,在第2个标签集继续中解析获取叫span的标签,有效阅读数的内容藏在第四个span标签中。

关于BeautifulSoup的find_all()find()的官方使用说明:

find_all(name, attrs, recursive, text, **kwargs)
find_all()方法搜索当前tag的所有tag子节点,并判断是否符合过滤器的条件.

find( name , attrs , recursive , text , **kwargs )
find_all() 方法将返回文档中符合条件的所有tag,尽管有时候我们只想得到一个结果.比如文档中只有一个<body>标签,那么使用 find_all() 方法来查找<body>标签就不太合适, 使用 find_all 方法并设置 limit=1 参数不如直接使用 find() 方法.

现在让我们来实例化一个爬虫,满怀憧憬的按下F5,让他跑起来。

getCaseSN = BeautifulGetCaseSN()  #创建类的实例,根据caseId爬取文章基础信息
getCaseSN.get_data()
print('爬取具体信息over')

在调试控制台里查看结果。
哇!爬取数据成功!第一个小爬虫诞生了。

然鹅!现在还不能开始庆祝,毕竟任务才进行到一半。接下来,我们根据需求,还需要爬取文章的评论者的名字做爬取统计。继续对网页进行分析。

需要对上图中的评论者姓名进行爬取,而且还需要做到翻页。

通过对请求的分析,发现这个评论块是个独立的请求,然后加入到文章页面的<frame>标签块中。点进去发现这是个POST请求,带有Form数据。通过对数据分析,发现有3个元素,第一个是评论的排序方式,我们不用动他,第二个是页码,就是翻页的关键参数,第三个是文章的Id。

开始构建POST请求

# 爬取具体信息
class BeautifulGetData():
    def __init__(self):  #类的初始化操作
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.221 Safari/537.36 SE 2.X MetaSr 1.0',
            'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
            'Accept-Language':'zh-CN,zh;q=0.8',
            'Connection':'keep-alive',
            'Cookie':'JSESSIONID=48A82C60BE65ED14C5978297C03AF776; PHPSESSID=ST-10-QybrBt31XVfPBqKAT2jr''
        }
        #要访问的网页地址
        self.web_url = 'http://net.chinamobile.com/cmccnkms-case/web/casesPortal/loadComments.action'  

    def get_data(self):
        print('开始文章评论内容POST请求')
        print('具体评论获取\n')
        for x in range(1,10):
            #post请求的数据
            web_data = {
                'sort':'desc',
                'pageNum':x,
                'caseId':75058
            }
            r = requests.post(self.web_url,data=web_data, headers=self.headers)

这里增加一个for循环就是为了模拟请求的页码,根据pageNum的不同,对该网址进行多次请求获取不同页面信息。

通过分析评论页的HTML数据,我发现每个评论都用<div class=“month”>包含,于是我们可以用find_all来获取全部的这个class,因为每页都有五个评论,所以可以用for循环来进行分析并输出。

下面是完整的请求代码

def get_data(self):
        print('开始文章评论内容POST请求')
        print('具体评论获取\n')
        get_name = []
        get_next_name = []
        for x in range(1,10):
            web_data = {
                'sort':'desc',
                'pageNum':x,
                'caseId':75058
            }
            r = requests.post(self.web_url,data=web_data, headers=self.headers)
            all_a = BeautifulSoup(r.text, 'lxml').find_all('div','month')
            print('第',x,'页')            
            
            #将上页获取的评论记录并清空当前页
            get_name = get_next_name
            get_next_name = []

            for a in enumerate(all_a):                 
                str_name = a[1].text.split(':') #对获取的文本内容做切割
                get_next_name.append(str_name[0]) #将名字加入到当前获取记录中
            
            if get_name == get_next_name:
                print('完成')
                break
            else:
                for a in get_next_name:
                    print(a)

这里说一下我定义的两个list:get_nameget_next_name。之所以定义这两个是因为每篇文章的评论数量我是不知道的,所以我不能直接控制需要爬取的页数,只能尽可能大的写一个数。但是当页数小于我设定的页码值后会发生如下的数据重复显示事件。

于是我加入了这两个参数,来存放前一页的获取的数据,如果单页获取的数据与前一页获取的数据相同,那说明就是到了评论的最后一页,直接跳出循环,结束该篇文章的评论爬取。

好了,把这两个类实例化一下,然后开始run起来吧。

getCaseSN = BeautifulGetCaseSN()  #创建类的实例,根据caseId爬取文章基础信息
getData = BeautifulGetData()  #创建类的实例,根据caseId爬取文章评论信息
getCaseSN.get_data()
getData.get_data()

成功获取到了我希望得到的目标数据,完美!

后记

但是,只对一篇固定文章的爬取,远远不是我的最终目的,我的目的是,导入一份需要爬取的表格,然后进行自动的爬取,并将获取的数据输出并保存下来。在下一篇文章中,我就来讲一讲,Excel文件的导入读取与文件的导出保存。

上一篇下一篇

猜你喜欢

热点阅读