Python爬虫第三课 网页爬取

Posted 笔触狂放

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python爬虫第三课 网页爬取相关的知识,希望对你有一定的参考价值。

静态网页是网站建设的基础,早期的网站基本都是由静态网页构成的。静态网页通常为纯粹的html格式,也可以包含一部分动态效果,如GIF格式的动画,Flash,滚动字幕等,该类网页的文件扩展名为.htm,.html。静态网页通常没有后台数据库,页面不含有程序并且无法交互。静态网页无法实时更新,更新页面时需要重新发布,通常适用于更新较少的展示型网站。本章将分别使用urllib3库,Requests库向网站发送HTTP请求并获取响应内容,再分别使用Chrome开发者工具,正则表达式,Xpath和Beautiful Soup解析获取的网页内容,最后将解析后的结果分别用Json模块,Pymysql库进行存储。

3.1 实现HTTP请求

一个爬虫的基本功能是读取URL和抓取网页内容,这就需要爬虫具备能够实现HTTP请求的功能。请求过程包括生成请求,请求头处理,超时设置,请求重试,查看状态码等。分别通过urllib3库,Requests库实现向网站发送GET类型的HTTP请求,并获取返回的响应。

使用urllib3库实现

许多Python的原生系统已经开始使用urllib3库,其提供了很多python标准库里所没有的重要特性。

连接特性连接特性
线程安全管理连接池
客户端SSL∕TLS验证使用分部编码上传文件
协助处理重复请求和HTTP重定位支持压缩编码
支持HTTP和SOCKS代理测试覆盖率达到100%

urllib3库的下载和安装

按住window+R键,在弹出的搜索框中输入cmd,打开dos窗口

 输入指令,使用pip下载urllib3库

已下载完成,在pycharm开发软件中查看该第三方库是否下载完毕。

在系统设置中能看到我们下载好的urllib3库,说明就下载好了

接着就能直接使用了。

1. 生成请求

通过request方法即可创建一个请求,该方法返回一个HTTP响应对象。request语法格式如下。

urllib3.request(method,url,fields=None,headers=None,urlopen_kw)

reques方法常用的参数及其说明如下。

参数说明
method接收string。表示请求的类型,如“GET”、“HEAD”、“DELETE”等。无默认值
url接收string。表示字符串形式的网址。无默认值
fields接收dict。表示请求类型所带的参数。默认为None
headers接收dict。表示请求头所带参数。默认为None
urlopen_kw接收dict或其他Python中的类型的数据。依据具体需要及请求的类型可添加的参数,通常参数赋值为字典类型或为具体数据。无默认值

那我们通过代码来演示效果:

# 导入urllib3库
import urllib3
# 创建PoolManager对象
http=urllib3.PoolManager()
# 通过调用request方法使用get请求访问该网站地址
rq=http.request("GET","http://www.tipdm.com/tipdm/index.html")
# 查看服务器响应码
print("服务器响应码:",rq.status)
# 服务器响应码: 200
# 查看响应实体 将页面内容从字节按UTF-8编码格式进行编码输出
print("获得网页源代码:",rq.data.decode("UTF-8"))

 运行结果如下:

通过获取的源码可以和网页的查看源代码进行对比是否是一致的。那么这就是爬虫的强大。  

2. 请求头处理

在request方法中,如果需要传入headers参数,可通过定义一个字典类型实现。定义一个包含User-Agent信息的字典,使用浏览器为火狐和chrome浏览器,操作系统为“Windows NT 6.1; Win64; x64”,向网站发送带headers参数的GET请求,hearders参数为定义的User-Agent字典。

# 使用请求头访问地址
ua = 'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) Chrome/65.0.3325.181'
rq = http.request('GET','http://www.tipdm.com/tipdm/index.html',headers = ua)
print("服务器响应码:",rq.status)
print("获得网页源代码:",rq.data.decode("UTF-8"))

浏览器是通过User-Agent参数来判断是正常用户的访问,还是非正常用户的访问。因此我们使用爬虫的时候,避免浏览器拦截我们的爬虫请求的情况下,需要加上请求头参数访问地址。

3. Timeout设置

为防止因为网络不稳定、服务器不稳定等问题造成连接不稳定时的丢包,可以在请求中增加timeout参数设置,通常为浮点数。依据不同需求,timeout参数提供多种设置方法,可直接在URL后设置该次请求的全部timeout参数,也可分别设置该次请求的连接与读取timeout参数,在PoolManager实例中设置timeout参数可应用至该实例的全部请求中。

# 直接在url参数之后添加统一的timeout参数
# 写法一
url = 'http://www.tipdm.com/tipdm/index.html'
rq = http.request('GET',url,timeout = 3.0)
# 分别设置连接与读取的timeout参数
# 写法二
rq = http.request('GET',url,timeout = urllib3.Timeout(connect = 1.0 , read = 3.0))
# 在PoolManager实例中设置timeout参数
# 写法三
http = urllib3.PoolManager(timeout = 4.0)
http = urllib3.PoolManager(timeout = urllib3.Timeout(connect = 1.0 , read = 3.0))

需要注意的是:如果已经在PoolManager对象中设置了timeout参数,则之后在request方法中另行设置的timeout参数将会替代PoolManager对象中的timeout方法。

4.请求重试设置

urllib3库可以通过设置retries参数对重试进行控制。默认进行3次请求重试,并进行3次重定向。自定义重试次数通过赋值一个整型给retries参数实现,可通过定义retries实例来定制请求重试次数及重定向次数。若需要同时关闭请求重试及重定向则可以将retries参数赋值为False,仅关闭重定向则将redirect参数赋值为False。与Timeout设置类似,可以在PoolManager实例中设置retries参数控制全部该实例下的请求重试策略。

# 直接在url之后添加retries参数
url = 'http://www.tipdm.com/tipdm/index.html'
rq = http.request('GET',url, retries = 10)
# 分别设置5次请求重试次数与4次重定向的retries参数
rq = http.request('GET',url, retries = 5 , redirect = 4)
# 同时关闭请求重试与重定向
rq = http.request('GET',url, retries = False)
# 仅关闭重定向
rq = http.request('GET',url, redirect = False)
# 在PoolManager实例中设置retries参数
http = urllib3.PoolManager(retries = 5)
http = urllib3.PoolManager(timeout = urllib3.Retry(5 , read = 4))

5. 生成完整HTTP请求

使用urllib3库实现生成一个完整的请求,该请求应当包含链接、请求头、超时时间和重试次数设置。

# 创建PoolManager实例
http = urllib3.PoolManager()
# 目标url
url = 'http://www.tipdm.com/tipdm/index.html'
# 设置请求头,UA信息
ua = 'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) Chrome/65.0.3325.181'
# 设置超时时间
tm = urllib3.Timeout(connect = 1.0 , read = 3.0)
# 设置重试次数并生成请求
rq = http.request('GET',url, headers = ua,timeout = tm, retries = 5 , redirect = 4)
# 查看服务器响应码
print("服务器响应码:", rq.status)
# 查看获取的内容
print("获取的内容:", rq.data.decode('utf-8'))

使用requests库实现

requests库是一个原生的HTTP库,比urllib3库更为容易使用。requests库发送原生的HTTP 1.1请求,无需手动为URL添加查询字串,也不需要对POST数据进行表单编码。相对于urllib3库,requests库拥有完全自动化Keep-alive和HTTP连接池的功能。requests库包含的特性如下。

连接特性连接特性连接特性
Keep-Alive&连接池基本∕摘要式的身份认证文件分块上传
国际化域名和URL优雅的key∕value Cookie流下载
带持久Cookie的会话自动解压连接超时
浏览器式的SSL认证Unicode响应体分块请求
自动内容解码HTTP(S)代理支持支持.netrc

该库也是第三方库,需要通过pip进行下载和安装,这里的操作过程就不再重复了,按照urllib3库的下载操作执行一遍即可。

1. 生成请求

requests库生成请求的代码非常便利,其使用的request方法的语法格式如下。

requests.request.method(url,**kwargs)

request方法常用的参数及其说明如下。

参数说明
method接收string。表示请求的类型,如“GET”、“HEAD”、“DELETE”等。无默认值
url接收string。表示字符串形式的网址。无默认值
**kwargs接收dict或其他Python中的类型的数据。依据具体需要及请求的类型可添加的参数,通常参数赋值为字典类型或为具体数据
import requests
url = 'http://www.tipdm.com/tipdm/index.html'
# 生成GET请求
rqg = requests.get(url)
print("结果类型:", type(rqg)) # 查看结果类型
print("状态码:", rqg.status_code)    # 查看状态码
print("编码:", rqg.encoding)     # 查看编码
print("响应头:", rqg.headers)      # 查看响应头
print("网页内容:",rqg.text) #查看网页内容

在以上代码中Requests库仅用一行代码便可生成GET请求,操作比urllib3库简洁。

2. 查看状态码与编码

需要注意的是,当requests库猜测错时,需要手动指定encoding编码,避免返回的网页内容解析出现乱码。

url = 'http://www.tipdm.com/tipdm/index.html'
rqg = requests.get(url)
print("状态码:", rqg.status_code)   # 查看状态码
print("编码:", rqg.encoding)     # 查看编码
rqg.encoding  = 'GBK'  # 手动指定编码
print("修改后的编码:", rqg.encoding)     # 查看修改后的编码

手动指定的方法并不灵活,无法自适应对应爬取过程中不同网页的编码,而使用chardet库比较简便灵活,chardet库是一个非常优秀的字符串∕文件编码检测模块。(需要先下载该库)

chardet库使用detect方法检测给定字符串的编码,detect方法常用的参数及其说明如下。

参数说明
byte_str接收string。表示需要检测编码的字符串。无默认值
import chardet
url = 'http://www.tipdm.com/tipdm/index.html'
rqg = requests.get(url)
print("编码:", rqg.encoding)     # 查看编码
print("detect方法检测结果:", chardet.detect(rqg.content))
rqg.encoding = chardet.detect(rqg.content)['encoding']  # 将检测到的编码赋值给rqg.encoding
print("改变后的编码:", rqg.encoding)     # 查看改变后的编码

3. 请求头与响应头处理

requests库中对请求头的处理与urllib3库类似,也使用headers参数在GET请求中上传参数,参数形式为字典。使用headers属性即可查看服务器返回的响应头,通常响应头返回的结果会与上传的请求参数对应。

url = 'http://www.tipdm.com/tipdm/index.html'
headers = 'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) Chrome/65.0.3325.181'
rqg = requests.get(url,headers = headers)
print("响应头:", rqg.headers)      # 查看响应头

响应头: 'Server': 'nginx', 'Date': 'Wed, 10 Aug 2022 08:05:51 GMT', 'Content-Type': 'text/html; charset=utf-8', 'Transfer-Encoding': 'chunked', 'Connection': 'keep-alive', 'Vary': 'Accept-Encoding', 'ETag': 'W/"15693-1585297141000"', 'Last-Modified': 'Fri, 27 Mar 2020 08:19:01 GMT', 'Content-Encoding': 'gzip'

4. Timeout设置

为避免因等待服务器响应造成程序永久失去响应,通常需要给程序设置一个时间作为限制,超过该时间后程序将会自动停止等待。在requests库中通过设置timeout这个参数实现,超过该参数设定的秒数后,程序会停止等待。

url = 'http://www.tipdm.com/tipdm/index.html'
print("超时时间为2:", requests.get(url, timeout=2))
requests.get(url, timeout=0.001)         # 由于超时时间过短将会报错

5. 生成完整HTTP请求

使用requests库的request方法向网站发送一个完整的GET请求,该请求包含链接、请求头、响应头、超时时间和状态码,并且编码应正确设置。

import chardet
# 设置url
url = 'http://www.tipdm.com/tipdm/index.html'
# 设置请求头
headers= 'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) Chrome/65.0.3325.181'
# 生成GET请求
rqg = requests.get(url, headers = headers, timeout=2)
print("状态码:", rqg.status_code)   # 查看状态码
print("编码:", rqg.encoding)     # 查看编码
# 修正编码
rqg.encoding = chardet.detect(rqg.content)['encoding']
print("修改后的编码:", rqg.encoding)     # 查看修改后的编码
print("响应头:", rqg.headers)      # 查看响应头
print(rqg.text)         # 查看网页内容

3.2 解析网页

通过解析网页可以获取网页包含的数据信息,如文本,图片,视频等,这需要爬虫具备定位网页中信息的位置并解析网页内容的功能。通过Chrome开发者工具直接查看网站的页面元素,页面源码和资源星系信息,分别通过正则表达式,Xpath及Beautiful Soup解析通过Requests库获取的网站的页面内容,获取其中的元素及相关信息。

使用chrome开发者工具查看网页

chrome浏览器提供了一个非常便利的开发者工具,供广大web开发者使用,该工具提供包括查看网页元素、查看请求资源列表、调试JS等功能。该工具其中一个打开方式可通过右键单击chrome浏览器页面,在弹出菜单中单击图所示的“检查”选项打开。

也可以单击chrome浏览器右上角快捷菜单,如图所示,单击“更多工具”选项中的“开发者工具”选项,或使用快捷键组合Ctrl+Shift+I。  

chrome开发者工具目前包括了9个面板,界面如图所示。  chrome开发者工具各面板功能如下。

面板说明
元素面板(Elements)该面板可查看渲染页面所需的HTML、CSS和DOM(Document Object Model)对象,并可实时编辑这些元素调试页面渲染效果
控制台面板(Console)该面板记录各种警告与错误信息,并可作为shell在页面上与javascript交互
源代码面板(Sources)该面板中可以设置断点调试JavaScript
网络面板(Network)该面板可查看页面请求、下载的资源文件及优化网页加载性能。还可查看HTTP的请求头、响应内容等
性能面板(Performance)原旧版chrome中的时间线面板(Timeline),该页面展示页面加载时所有事件花费时长的完整分析
内存面板(Memory)原旧版chrome中的分析面板(Profiles),提供比性能面板更详细的分析,如可跟踪内存泄露等
应用面板(Application)原旧版chrome中的资源面板(Profiles),该面板可检查加载的所有资源
安全面板(Security)该面板可调试当前网页的安全和认证等问题并确保网站上已正确地实现HTTPS
审查面板(Audits)该面板对当前网页的网络利用情况、网页性能方面进行诊断,并给出优化建议

对于爬虫开发来说,常用的面板为元素面板,源代码面板和网络面板。

使用正则表达式解析网页

在编写处理网页文本的程序时,经常会有查找符合某些复杂规则的字符串的需求,而正则表达式正好能满足这一点。正则表达式(Regular Expression)简称Regex或RE,又称为正则表示法或常规表示法,常常用于检索,替换符合某个模式的文本。

1. Python正则表达式模块

使用re的步骤为:先将正则表达式的字符串形式编译为Pattern实例;然后使用Pattern实例处理文本并获得匹配结果(一个Match实例);最后使用Match实例获得信息,进行其他的操作。re模块中常用的方法及其说明如下。

方法说明
compile将正则表达式的字符串转化为Pattern匹配对象
match将输入的字符串从头开始对输入的正则表达式进行匹配,一直向后直至遇到无法匹配的字符或到达字符串末尾,将立即返回None,否则获取匹配结果
search将输入的字符串整个扫描,对输入的正则表达式进行匹配,获取匹配结果,否则输出None
split按照能够匹配的字符串作为分隔符,将字符串分割后返回一个列表
findall搜索整个字符串,返回一个列表包含全部能匹配的子串
finditer与findall方法作用类似,以迭代器的形式返回结果
sub使用指定内容替换字符串中匹配的每一个子串内容

compile方法

re模块中使用compile方法将正则表达式的字符串转化为Pattern匹配对象,compile方法的语法格式如下。

re.compile(string[,flag])

compile方法常用的参数及其说明如下。

参数说明
string接收string。表示需要转换的正则表达式的字符串。无默认值
flag接收string。表示匹配模式,取值为运算符“|”时表示同时生效,如re.I|re.M。默认为None

flag参数的可选值如下。  

可选值说明
re.I忽略大小写
re.M多行模式,改变“^”和“$”的行为
re.S“.”任意匹配模式,改变“.”的行为
re.L使预定字符类\\w\\W\\b\\B\\s\\S取决与当前区域设定
re.U使预定字符类\\w\\W\\b\\B\\s\\S\\d\\D取决于unicode定义的字符属性
re.X详细模式,该模式下正则表达式可为多行,忽略空白字符并可加入注释

search方法

earch方法将输入的字符串整个扫描,对输入的正则表达式进行匹配,若无可匹配字符,将立即返回None ,否则获取匹配结果,search方法的语法格式如下。

re.search(pattern,string[,**flags])

search方法常用的参数及其说明如下。

参数说明
pattern接收Pattern实例。表示转换后的正则表达式。无默认值
string接收string。表示输入的需要匹配的字符串。无默认值
flag接收string。表示匹配模式,取值为运算符“|”时表示同时生效,如re.I|re.M。默认为None
import re
# 搜索出字符串中所有的数字
pat = re.compile(r'\\d+')   # 转换用于匹配数字的正则表达式
print("成功匹配:", re.search(pat,'abc45'))   # 成功匹配到45
输出结果为:
    成功匹配: <re.Match object; span=(3, 5), match='45'>

findall方法

findall方法搜索整个string,返回一个列表包含全部能匹配的子串,其语法格式如下。

re.findall(pattern,string**[,flags])

findall方法常用的参数及其说明如下

参数说明
pattern接收Pattern实例。表示转换后的正则表达式。无默认值
string接收string。表示输入的需要匹配的字符串。无默认值
flag接收string。表示匹配模式,取值为运算符“|”时表示同时生效,如re.I|re.M。默认为None
import re
# 找出字符串中所有的数字,返回为列表
pat = re.compile(r'\\d+')   # 转换用于匹配数字的正则表达式
print("成功找出:", re.findall(pat,'ab2c3ed'))   # 将找出其中的2、3
for i in re.findall(pat,'ab2c3ed'):
    print(i)

2. 获取网页中的标题内容

分别使用re库中search方法和findall方法查找使用requests库获取的网页内容中的title内容。

使用正则表达式无法很好的定位特定节点并获取其中的链接和文本内容,而使用Xpath和Beautiful Soup能较为便利的实现这个功能。

import re
# 调用requests库获取的网页
import requests
import chardet
url = 'http://www.tipdm.com/tipdm/index.html'
ua = 'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) Chrome/65.0.3325.181'
rqg = requests.get(url,headers = ua)
rqg.encoding = chardet.detect(rqg.content)['encoding']
# 方法一:使用search方法查找title中的内容
title_pattern = r'(?<=<title>).*?(?=</title>)'
title_com = re.compile(title_pattern,re.M|re.S)
title_search = re.search(title_com,rqg.text)
title = title_search.group()
print("标题内容:", title)
# 方法二:使用findall方法查找title中的内容
print("标题内容:", re.findall(r'<title>(.*?)</title>',rqg.text))

运行结果:

标题内容: 泰迪科技-专注于大数据技术研发及知识传播 标题内容: ['泰迪科技-专注于大数据技术研发及知识传播']

使用正则表达式无法很好地定位特定节点并获取其中的链接和文本内容,而使用Xpath和Beautiful Soup能较为便利的实现这个功能。

使用Xpath解析网页

XML路径语言(XML Path Language,XPath)是一门在XML文档中查找信息的语言。XPath最初被设计用来搜寻XML文档,但是同样适用于HTML文档的搜索。XPath的选择功能十分强大,它提供了非常简洁明了的路径选择表达式,还提供了超过100个内建函数,用于字符串,数值,时间的匹配,以及节点,序列的处理等等,几乎所有定位的节点都可以用XPath来选择。要使用该库也是先要通过pip进行下载安装后才能使用。这里下载的库为lxml库,而并不是XPath。

1. 基本语法

使用XPath需要从lxml库中导入etree模块,还需要使用HTML类对需要匹配的HTML对象进行初始化。HTML类的基本语法格式如下:

lxml.etree.HTML(text,parser=None,*,base_url=None)

HTML类的常用参数及其说明如下:

参数名称说明
text接收str。表示需要转换为HTML的字符串。无默认值
parser接收str。表示选择的HTML解析器。无默认值
base_url接收str。表示文档的原始URL,用于查找外部实体的相对路径。默认为None
# 导入etree模块
from lxml import etree
# 调用requests库获取的网页
import requests
url = 'http://www.tipdm.com/tipdm/index.html'
ua = 'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) Chrome/65.0.3325.181'
rqg = requests.get(url,headers = ua)
# 初始化HTML
html = rqg.content
html = etree.HTML(html,parser=etree.HTMLParser(encoding='utf-8'))
# 输出修正后的HTML(如有必要)
result = etree.tostring(html,encoding='utf-8',pretty_print=True, method="html")
print("修正后的HTML:", result)

输出的数据是字节,如果需要获得字符串,则需要调用decode函数转换编码格式。

如果需要加载本地的html文件的话,XPath可能做到。

# 导入etree模块
from lxml import etree
# 从本地文件导入,test.html为保存的使用requests库获取的网页
html_local = etree.parse('./test.html', etree.HTMLParser(encoding='utf-8'))
result = etree.tostring(html_local)
print("本地文件导入的HTML:", result)

Xpath使用类似正则的表达式来匹配HTML文件中的内容,常用匹配表达式如下。

表达式说明
nodename选取nodename节点的所有子节点
/从当前节点选取直接子节点
//从当前节点选取子孙节点
.选取当前节点
..选取当前节点的父节点
@选取属性

子节点表示当前节点的下一层节点,子孙节点表示当前节点的所有下层节点,父节点表示当前节点的上一层节点。

# 导入etree模块
from lxml import etree
# 调用requests库获取的网页
import requests
url = 'http://www.tipdm.com/tipdm/index.html'
ua = 'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) Chrome/65.0.3325.181'
rqg = requests.get(url,headers = ua)
# 初始化HTML
html = rqg.content
html = etree.HTML(html,parser=etree.HTMLParser(encoding='utf-8'))
# 通过名称定位head节点
result = html.xpath('head')
print("名称定位结果:", result)
# 按节点层级定位title节点
result1 = html.xpath('/html/head/title')
print("节点层级定位结果:", result1)
# 通过名称定位title节点
result2 = html.xpath('title')
print("名称定位title节点结果:", result2)
# 另一种方式定位title节点,当前节点为html节点,title是html节点的孙节点,因此需要加双斜杠
result3 = html.xpath('//title')
print("搜索定位title节点结果:", result3)

输出结果:

名称定位结果: [<Element head at 0x365c4c8>]

节点层级定位结果: [<Element title at 0x365c508>]

名称定位title节点结果: [] 搜索定位title节点结果: [<Element title at 0x365c508>]

以上代码中,直接使用名称无法定位子孙节点的title节点,因为名称只能定位子节点的head节点或body节点。

2. 谓语

Xpath中的谓语用来查找某个特定的节点或包含某个指定的值的节点,谓语被嵌在路径后的方括号中,如下。

表达式说明
/html/body/div[1]选取属于body子节点下的第一个div节点
/html/body/div[last()]选取属于body子节点下的最后一个div节点
/html/body/div[last()-1]选取属于body子节点下的倒数第二个div节点
/html/body/div[positon()<3]选取属于body子节点下的下前两个div节点
/html/body/div[@id]选取属于body子节点下的带有id属性的div节点
/html/body/div[@id=”content”]选取属于body子节点下的id属性值为content的div节点
/html /body/div[xx>10.00]选取属于body子节点下的xx元素值大于10的节点
# 定位header节点
result1 = html.xpath('//header[@class]')
print("class属性定位结果:", result1 )
# 定位ul节点
result2 = html.xpath('//ul[@id="menu"]')
print("id属性定位结果:", result2)

输出结果:

class属性定位结果: [<Element header at 0x365dbc8>]

id属性定位结果: [<Element ul at 0x365db48>]

3. 功能函数

Xpath中还提供功能函数进行模糊搜索,有时对象仅掌握了其部分特征,当需要模糊搜索该类对象时,可使用功能函数来实现,具体函数如下。

功能函数示例说明
starts-with//div[starts-with(@id,”co”)]选取id值以co开头的div节点
contains//div[contains(@id,”co”)]选取id值包含co的div节点
and//div[contains(@id,”co”)andcontains(@id,”en”)]选取id值包含co和en的div节点
text()//li[contains(text(),”first”)]选取节点文本包含first的div节点
# 获取title节点的文本内容
title = html.xpath('//title/text()')
print("title节点的文本内容:", title)

4. 提取header节点下全部标题文本及对应链接

使用text方法可以提取某个单独子节点下的文本,若想提取出定位到的子节点及其子孙节点下的全部文本,则需要使用string方法实现。

使用HTML类将其初始化通过requests库获取的网页,之后使用谓语定位id值以me开头的ul节点,并使用text方法获取其所有子孙节点a内的文本内容,使用@选取href属性从而实现提取所有子孙节点a内的链接,最后使用string方法直接获取ul节点及其子孙节点中的所有文本内容。

# 定位id值以me开头的ul节点并提取其所有子孙节点a内的文本内容
content=html.xpath('//ul[starts-with(@id,"me")]/li//a/text()')
for i in content:
    print(i )
# 结果:
	首页
	产品中心
	学科建设
	培训认证
	企业应用
	校企合作
	新闻中心
	关于我们
# 提取对应链接
url_list = html.xpath('//ul[starts-with(@id,"me")]/li//a/@href')
for i in url_list:
    print(i)
# 结果:
/
/tipdm/cpzx/
http://www.tipdm.com:80/xkjs/index.jhtml
http://www.tipdm.com:80/pxrz/index.jhtml
http://www.tipdm.com:80/qyyy/index.jhtml
http://www.tipdm.com:80/xqhz/index.jhtml
http://www.tipdm.com:80/xwzx/index.jhtml
/tipdm/gywm/
# 定位id值以me开头的ul节点
target=html.xpath('//ul[starts-with(@id,"me")]')
# 提取该节点下的全部文本内容
target_text = target[0].xpath('string(.)').strip()    # strip方法用于去除多余的空格
print("节点下的全部文本内容:", target_text)
# 结果:
	首页
	产品中心
	学科建设
	培训认证
	企业应用
	校企合作
	新闻中心
	关于我们

对于XPath库的使用需要结合Requests库的地址访问进行熟练掌握解析网页的数据。多多练习!!!

使用Beautiful Soup解析网页

Beautiful Soup是一个可以从HTML或XML文件中提取数据的Python库。目前Beautiful Soup 3已经停止开发,大部分的爬虫选择使用Beautiful Soup 4开发。该库的使用也是需要通过pip进行下载和安装的bs4.

Beautiful Soup不仅支持Python标准库中的HTML解析器,还支持一些第三方的解析器,具体语法如下。  

解析器语法格式优点缺点
Python标准库BeautifulSoup(markup, "html.parser")1.Python的内置标准库 2.执行速度适中 3.文档容错能力强Python 2.7.3或3.2.2前的版本中文档容错能力差
lxml HTML解析器BeautifulSoup(markup, "lxml")1.速度快 2.文档容错能力强需要安装C语言库
lxml XML解析器BeautifulSoup(markup, ["lxml-xml"]) BeautifulSoup(markup, "xml")1.速度快 2.唯一支持XML的解析器需要安装C语言库
html5libBeautifulSoup(markup, "html5lib")1.最好的容错性 2.以浏览器的方式解析文档 3.生成HTML5格式的文档速度慢 不依赖外部扩展

1. 创建BeautifulSoup对象

要使用Beautiful Soup库解析网页首先需要创建BeautifulSoup对象,将字符串或HTML文件传入Beautiful Soup库的构造方法可以创建一个BeautifulSoup对象,使用格式如下。

BeautifulSoup("<html>data</html>") #通过字符串创建

BeautifulSoup(open("index.html")) #通过HTML文件创建

生成的BeautifulSoup对象可通过prettify方法进行格式化输出,其语法格式如下。

BeautifulSoup.prettify**(self, encoding=None, formatter='minimal')

prettify方法常用的参数及其说明如下。

参数说明
encoding接收string。表示格式化时使用的编码。默认为None
formatter接收string。表示格式化的模式。默认为minimal,表示按最简化的格式化将字符串处理成有效的HTML∕XML
# BeautifulSoup的使用
from bs4 import BeautifulSoup
import requests
url = 'http://www.tipdm.com/tipdm/index.html'
ua = 'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) Chrome/65.0.3325.181'
# 发出访问请求并携带请求头参数信息
rqg=requests.get(url,headers=ua)
# 将获取的字节源代码转换成UTF-8编码格式
html=rqg.content.decode("UTF-8")
# 将源代码使用XPath作为解析器解析为BeautifulSoup库的对象
soup=BeautifulSoup(html,"lxml")
# 并将内容进行格式化后输出
print("输出格式化的BeautifulSoup对象:",soup.prettify())

大家自行编写运行查看效果,输出的是该页面的源码。

2. 对象类型

Tag对象类型

•Tag对象为HTML文档中的标签,形如“<title>The Dormouse's story</title>”或“<p class="title">The Dormouse's story</p>”等HTML标签再加上其中包含的内容便是Beautiful Soup中的Tag对象。

•通过Tag的名称属性可以很方便的在文档树中获取需要的Tag对象,通过该方法只能获取文档树中第一个同名的Tag对象,而通过多次调用可获取某个Tag对象下的分支Tag对象。通过find_all方法可以获取文档树中的全部同名Tag对象。

print("获取head标签:", soup.head)    # 获取head标签
soup.title  # 获取title标签
print("获取第一个a标签:", soup.body.a)   # 获取body标签中的第一个a标签
print("所有名称为a的标签的个数:" , len(soup.find_all('a')))    # 获取所有名称为a的标签的个数

•Tag有两个非常重要的属性:name和attributes。name属性可通过name方法来获取和修改,修改过后的name属性将会应用至BeautifulSoup对象生成的HTML文档。

print("soup的name:" , soup.name)    # 获取soup的name
print("a标签的name:" , soup.a.name)     # 获取a标签的name
tag = soup.a
print("tag的name:" , tag.name)       # 获取tag的name
print ("tag的内容:" , tag)
tag.name = 'b'    # 修改tag的name
print ("修改name后tag的内容:" , tag)            # 查看修改name后的HTML
print("Tag对象的全部属性:" , tag.attrs)    # 获取Tag对象的全部属性
print("class属性的值:" , tag['class'])     # 获取class属性的值
tag['class'] = 'Logo'  # 修改class属性的值
print("修改后Tag对象的属性:" , tag.attrs)
tag['id'] = 'logo'  # 新增属性id,赋值为logo
del tag['class']      # 删除class属性
print ("修改后tag的内容:" , tag)

NavigableString对象类型

NavigableString对象为包含在Tag中的文本字符串内容,如“<title>The Dormouse‘s story</title>”中的“The Dormouse’s story”,使用string的方法获取,NavigableString对象无法被编辑,但可以使用replace_with的方法进行替换。

tag = soup.title
print ("Tag对象中包含的字符串:" , tag.string)    # 获取Tag对象中包含的字符串
print ("tag.string的类型:" , type(tag.string))     # 查看类型
tag.string.replace_with("泰迪科技")   # 替换字符串内容
print ("替换后的内容:" , tag.string)

BeautifulSoup对象类型

BeautifulSoup对象表示的是一个文档的全部内容。大部分时候,可以把它当作Tag对象。 BeautifulSoup对象并不是真正的HTML或XML的tag,所以并没有tag的name和attribute属性,但其包含了一个值为“[document]”的特殊属性name。

print ("soup的类型:" , type(soup))     # 查看类型
print ("BeautifulSoup对象的特殊属性name:" , soup.name)    # BeautifulSoup对象的特殊属性name
print ("soup.name的类型:" , type(soup.name))
print ("BeautifulSoup对象的attribute属性:" , soup.attrs)     # BeautifulSoup对象的attribute属性为空

Comment对象类型

Tag、NavigableString、BeautifulSoup几乎覆盖了html和xml中的所有内容,但是还有一些特殊对象,文档的注释部分是最容易与Tag中的文本字符串混淆的部分。Beautiful Soup库中将文档的注释部分识别为Comment类型,Comment对象是一个特殊类型的NavigableString对象,但是当其出现在HTML文档中时,Comment对象会使用特殊的格式输出,需调用prettify方法。

markup = "<c><!--This is a markup--></c>"
soup_comment = BeautifulSoup(markup, "lxml")
comment = soup_comment.c.string    # comment对象也由string方法获取
print ("注释的内容:" , comment)               # 直接输出时与一般NavigableString对象一致
print ("注释的类型:" , type(comment))          # 查看类型

3. 搜索特定节点并获取其中的链接及文本

Beautiful Soup定义了很多搜索方法,其中常用的有find方法和find_all方法,两者的参数一致,区别为find_all方法的返回结果是值包含一个元素的列表,而find直接返回的是结果。find_all方法用于搜索文档树中的Tag非常方便,其语法格式如下。

BeautifulSoup.find_all(name,attrs,recursive,string,**kwargs**)

find_all方法的常用参数及其说明如下。

参数说明
name接收string。表示查找所有名字为name的tag,字符串对象会被自动忽略掉,搜索name参数的值可以使用任一类型的过滤器:字符串、正则表达式、列表、方法或True。无默认值
attrs接收string。表示查找符合CSS类名的tag,使用class做参数会导致语法错误,从Beautiful Soup的4.1.1版本开始,可以通过class_参数搜索有指定CSS类名的tag。无默认值
recursive接收Built-in。表示是否检索当前tag的所有子孙节点。默认为True,若只想搜索tag的直接子节点,可将该参数设为False
string接收string。表示搜索文档中匹配传入的字符串的内容,与name参数的可选值一样,string参数也接收多种过滤器。无默认值
**kwargs若一个指定名字的参数不是搜索内置的参数名,搜索时会把该参数当作指定名字tag的属性来搜索

find_all方法可通过多种参数遍历搜索文档树中符合条件的所有子节点。

•可通过name参数搜索同名的全部子节点,并接收多种过滤器。

•按照CSS类名可模糊匹配或完全匹配。完全匹配class的值时,如果CSS类名的顺序与实际不符,将搜索不到结果。

•若tag的class属性是多值属性,可以分别搜索tag中的每个CSS类名。

•通过字符串内容进行搜索符合条件的全部子节点,可通过过滤器操作。

•通过传入关键字参数,搜索匹配关键字的子节点。

# 通过name参数搜索名为title的全部子节点
print ("名为title的全部子节点:" , soup.find_all("title "))
print ("title子节点的文本内容:" , soup.title.string)
print ("使用get_text()获取的文本内容:" , soup.title.get_text())
target = soup.find_all("ul", class_="menu")   # 按照CSS类名完全匹配
print("CSS类名匹配获取的节点:" , target)
target = soup.find_all(id='menu')            # 传入关键字id,按符合条件的搜索
print("关键字id匹配的节点:" , target)
target = soup.ul.find_all('a')
print("所有名称为a的节点:" , target)
# 创建两个空列表用于存放链接及文本
urls = []
text = []
# 分别提取链接和文本
for tag in target:
    urls.append(tag.get('href'))
    text.append(tag.get_text())
for url in urls:
    print(url)
for i in text:
    print(i)

3.3 数据存储

爬虫通过解析网页获取页面中的数据后,还需要将获得的数据存储下来以供后续分析。使用JSON模块将XPath获取的文本内容存储为JSON文件,使用PyMySQL库将Beautiful Soup库获取的标题存入MySQL数据库。

1. 将数据存储为JSON文件

将数据存储为JSON文件的过程为一个编码过程,编码过程常用dump函数和dumps函数。两者的区别在于,dump函数将Python对象转换为JSON对象,并通过fp文件流将JSON对象写入文件内,而dumps函数则生成一个字符串。dump函数和dumps函数的语法格式如下。

json.dump(obj,fp,skipkeys=False,ensure_ascii=True,check_circular=True,allow_nan=True,cls=None,indent=None,separators=None,encoding**='utf-8', default=None, sort_keys=False, **kw**)

json.dumps(obj,skipkeys**=False, ensure_ascii**=True, check_circular=True,allow_nan**=True,

cls=None,indent=None,separators=None,encoding**='utf-8', default=None, sort_keys=False, **kw**)

dump函数和dumps函数的常用参数及其说明如下。

参数说明
skipkeys接收Built-in。表示是否跳过非Python基本类型的key,若dict的keys内的数据为非Python基本类型,即不是str、unicode、int、long、float、bool、None等类型,设置该参数为False时,会报TypeError错误。默认值为False,设置为True时,跳过此类key
ensure_ascii接收Built-in。表示显示格式,若dict内含有非ASCII的字符,则会以类似“\\uXXX”的格式显示。默认值为True,设置为False后,将会正常显示
indent接收int。表示显示的行数,若为0或为None,则在一行内显示数据,否则将会换行且按照indent的数量显示前面的空白,将JSON内容格式化显示。默认为None
separators接收string。表示分隔符,实际上为(item_separator,dict_separator)的一个元组,默认为(',',':'),表示dictionary内的keys之间用“,”隔开,而key和value之间用“:”隔开。默认为None
encoding接收string。表示设置的JSON数据的编码形式,处理中文时需要注意此参数的值。默认为UTF-8
sort_keys接收Built-in。表示是否根据keys的值进行排序。默认为False,为True时数据将根据keys的值进行排序
import json
# 使用requests和Xpath获取数据
from lxml import etree
import requests
import chardet
url = 'http://www.tipdm.com/tipdm/index.html'
ua = 'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) Chrome/65.0.3325.181'
rqg = requests.get(url,headers = ua)
rqg.encoding = chardet.detect(rqg.content)['encoding']
html = rqg.content.decode('utf-8')
html = etree.HTML(html,parser=etree.HTMLParser(encoding='utf-8'))
content=html.xpath('//ul[starts-with(@id,"me")]/li//a/text()')
print("标题菜单的文本:", content)
# 使用dump方法写入文件,该执行的方法取别名
# with open('output.json','w') as fp:
#     json.dump(content,fp)
f=open("output.json","w")
json.dump(content,f)

2. 将数据存储入MySQL数据库

连接方法

pymysql模块使用connect方法连接数据库,connect方法的语法格式如下。

pymysql.connect(host,port,user,passwd,db,charset,connect_timeout,use_unicode**)

connect方法有很多参数可供使用,其常用的参数及其说明如下。

参数说明
host接收string。表示数据库地址,本机地址通常为127.0.0.1。默认为None
port接收string。表示数据库端口,通常为3306。默认为0
user接收string。数据库用户名,管理员用户为root。默认为None
passwd接收string。表示数据库密码。默认为None
db接收string。表示数据库库名。无默认值
charset接收string。表示插入数据库时的编码。默认为None
connect_timeout接收int。表示连接超时时间,以秒为单位。默认为10
use_unicode接收string。表示结果以unicode字符串返回。默认为None
import pymysql
# 使用参数名创建连接
conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='admin', db='test', charset='utf8',
 connect_timeout=1000)
# 不使用参数名创建连接
conn = pymysql.connect('127.0.0.1', 'root', 'admin', 'test')

数据库操作函数

pymysql中可以使用函数返回的连接对象connect进行操作,常用的函数如下。

函数说明
commit提交事务。对支持事务的数据库或表,若提交修改操作后,不使用该方法,则不会写入数据库中
rollback事务回滚。在没有commit前提下,执行此方法时,回滚当前事务
cursor创建一个游标对象。所有的sql语句的执行都需要在游标对象下进行

在python操作数据库的过程中,通常主要是使用connect.cursor方法获取游标,或使用cursor.execute方法对数据库进行操作,如创建数据库以及数据表等操作,通常使用更多的为增、删、改、查等基本操作。游标对象也提供了很多种函数,常用函数如下。

函数说明语法格式
close关闭游标close()
execute执行sql语句execute(sql)
excutemany执行多条sql语句excutemany(sql)
fetchone获取执行结果中的第一条记录fetchone()
fetchmany获取执行结果中的n条记录fetchmany(n)
fetchall获取执行结果的全部记录fetchall()
scroll用于游标滚动scroll()
import pymysql
# 使用参数名创建连接
conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='admin', db='test', charset='utf8', connect_timeout=1000)
# 创建游标
cursor=conn.cursor()
# 创建表
sql="""create table if not exists class (id int(10) primary key auto_increment,name varchar(20) not null,text varchar(20) not null)"""
cursor.execute(sql)            # 执行创建表的sql语句
cursor.execute("show tables")    # 查看创建的表
# 数据准备
import requests
import chardet
from bs4 import BeautifulSoup
url = 'http://www.tipdm.com/tipdm/index.html'
ua = 'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) Chrome/65.0.3325.181'
rqg = requests.get(url,headers = ua)
rqg.encoding = chardet.detect(rqg.content)['encoding']
html = rqg.content.decode('utf-8')
soup = BeautifulSoup(html, "lxml")
target = soup.title.string
print("标题的内容:", target)
# 插入数据
title = "tipdm"
sql = "insert into class (name,text)values(%s,%s)"
cursor.execute(sql,(title,target))          # 执行插入语句
conn.commit()           # 提交事务
# 查询数据
data=cursor.execute("select * from class")
# 使用fetchall方法获取操作结果
data=cursor.fetchmany()
print("查询获取的结果:", data)
conn.close()

小结

(1) 分别通过urllib3库和Requests库建立HTTP请求,从而与网站建立链接并获取网页内容。相比urllib3库,Requests库使用起来更为简洁直观。

(2) 使用Chrome开发者工具可方便地直接查看页面元素,页面源码及资源加载过程。

(3) 通过正则表达式可按照模式对网页内容进行匹配,查找符合条件的网页内容,缺点为不易上手且容易产生分歧。

(4) 通过lxml库中的etree模块实现使用Xpath解析网页。通过表达式及谓语可查找特定节点,也可提供功能函数

进行模糊查询和内容获取

(5) Beautiful Soup 库可从HTML或XML文件中提取数据,并可提供函数处理导航,搜索,修改分析树的功能。

(6) JSON模块可提供Python对象与JSON对象的互相转换功能,并可提供存储数据为JSON文件的功能。

(7) PyMySQL库可提供操作MySQL的功能,内含数据库连接方法及多种操作函数。

以上是关于Python爬虫第三课 网页爬取的主要内容,如果未能解决你的问题,请参考以下文章

爬虫第三课:互联网中网页的解析

Scrapy学习第三课

python 爬虫学习第三课

值得收藏的Python第三方库

Python爬虫爬取动态网页

Python爬虫第三课(提取数据)