在系列一中,我们重点学习了网页的基本组成与网页代码的简单分析,并且学习了requests库的实战操作。requests是python实现的最简单易用的HTTP库,因此强烈建议爬虫使用requests。系列一链接:【Python】爬虫入门强烈推荐系列一
解析和提取
我们通过requests可以很轻松地就获得网页上的所有内容,但是这些内容往往会夹杂着许多我们不需要的东西,因此我们需要解析和提取 HTML 数据。
本系列重点围绕三个库进行解析,按重要程度排名 分别先后进行介绍。重点介绍正则表达式,效率高,但复杂,在学术界和工业场景应用最多,建议硬啃,真的理解不了的新手可倒着看~。
正则表达式 re
re的定义
正则表达式是什么?
- 正则表达式是用来简洁表达一组字符串的表达式
- 正则表达式是一种通用的字符串表达框架
- 正则表达式是一种针对字符串表达“简洁”和“特征”思想的工具
- 简单来说,它是一种模式,把该模式应用于文本匹配,然后获取符合该模式的内容,当然,这些内容就是我们所需要的。
使用正则表达式可以干什么?
- 测试字符串内的模式。
例如,可以测试输入字符串,以查看字符串内是否出现电话号码模式或信用卡号码模式。这称为数据验证。 - 替换文本。
可以使用正则表达式来识别文档中的特定文本,完全删除该文本或者用其他文本替换它。 - 基于模式匹配从字符串中提取子字符串。
可以查找文档内或输入域内特定的文本;可以使用正则表达式来搜索和替换标记。
re的语法
正则表达式语法由字符和操作符构成:
常用操作符
符号 | 作用 |
---|---|
. | 表示任何单个字符 |
[ ] | 字符集,对单个字符给出取值范围 ,如[abc]表示a、b、c,[a‐z]表示a到z单个字符 |
[^ ] | 非字符集,对单个字符给出排除范围 ,如[^abc]表示非a或b或c的单个字符 |
* | 前一个字符0次或无限次扩展,如abc* 表示 ab、abc、abcc、abccc等 |
+ | 前一个字符1次或无限次扩展 ,如abc+ 表示 abc、abcc、abccc等 |
? | 前一个字符0次或1次扩展 ,如abc? 表示 ab、abc |
| | 左右表达式任意一个 ,如abc |
{m} | 扩展前一个字符m次 ,如ab{2}c表示abbc |
{m,n} | 扩展前一个字符m至n次(含n) ,如ab{1,2}c表示abc、abbc |
^ | 匹配字符串开头 ,如^abc表示abc且在一个字符串的开头 |
$ | 匹配字符串结尾 ,如abc$表示abc且在一个字符串的结尾 |
( ) | 分组标记,内部只能使用 |
\d | 数字,等价于[0‐9] |
\w | 单词字符,等价于[A‐Za‐z0‐9_] |
re的使用
- 调用方式:import re
- re库采用raw string类型表示正则表达式,表示为:r’text’,raw string是不包含对转义符再次转义的字符串
主要功能函数:
函数 | 功能 |
---|---|
re.search(pattern, string, flags=0) | 在一个字符串中搜索匹配正则表达式的第一个位置,返回match对象 |
re.match(pattern, string, flags=0) | 从一个字符串的开始位置起匹配正则表达式,返回match对象 |
re.findall(pattern, string, flags=0) | 搜索字符串,以列表类型返回全部能匹配的子串 |
re.split(pattern, string, maxsplit=0, flags=0) | 将一个字符串按照正则表达式匹配结果进行分割,返回列表类型 |
re.finditer(pattern, string, flags=0) | 搜索字符串,返回一个匹配结果的迭代类型,每个迭代元素是match对象 |
re.sub(pattern, repl, string, count=0, flags=0) | 在一个字符串中替换所有匹配正则表达式的子串,返回替换后的字符串 |
参数解析:
pattern:指的是正则表达式;
string:指的是我们需要进行操作的字符串;
flags : 正则表达式使用时的控制标记:
- re.I --> re.IGNORECASE : 忽略正则表达式的大小写,[A‐Z]能够匹配小写字符
- re.M --> re.MULTILINE : 正则表达式中的^操作符能够将给定字符串的每行当作匹配开始
- re.S --> re.DOTALL : 正则表达式中的.操作符能够匹配所有字符,默认匹配除换行外的所有字符
re的对象
生成re对象:
regex = re.compile(pattern, flags=0):将正则表达式的字符串形式编译成正则表达式对象
与直接调用函数进行比较:
1、re对象
content = 'Hello, I am Wuijekd, from Guangdong.'
regex = re.compile('\w*')
sentence = regex.match(content)
print(sentence.group())
[Output]:Hello
2、直接调用函数
sentence = re.match('\w*',content)
print(sentence.group())
[Output]:Hello
re 库的贪婪匹配和最小匹配
.* Re库默认采用贪婪匹配,即输出匹配最长的子串
*? 只要长度输出可能不同的,都可以通过在操作符后增加?变成最小匹配
content = 'Hello, I am Wuijekd, from Guangdong.'
sentence = re.match('\w.*',content)
print(sentence.group())
[Output]:Hello, I am Wuijekd, from Guangdong.
sentence = re.match('\w.?',content)
print(sentence.group())
[Output]:He
re的实战(淘宝网)
目标网站:淘宝商品比价定向爬虫
本次任务不是爬取简单的公开网站,而是需要进行登录的网站。
user-agent:浏览器请求头,伪装成浏览器访问。
cookie:某些网站为了辨别用户身份,进行Session跟踪而储存在用户本地终端上的数据(通常经过加密),由用户客户端计算机暂时或永久保存的信息。
获取cookie
因此requests的头参数要加上cookie,cookie获取方式如下:
一、打开网站,登录账号,然后F12打开开发者模式
二、点击控制台Console,输入 document.cookie
会打印出cookie,我认为这是最简单的方法啦~
三、但是以上获得的cookie并不能正常登录,应该是被加密过了。接下来介绍一种可以的方法~
开发者模式——》Network——》Doc
一般都会有文件显示的,没有文件(如上图所示)也不用担心,可以使用Command+R 加载出来
注意,看Domain,要选对域名。
四、获取cookie
完整代码
# 导入包
import requests
import re
def getHTMLText(url):
"""
请求获取html,(字符串)
:param url: 爬取网址
:return: 字符串
"""
try:
# 添加头信息,
kv = {
'cookie':"_samesite_flag_=true; cookie2=14eae9f8d1b1482a3024a8a0168082b8; t=6be116d8f4642ef31ff898e55ecb9439; _tb_token_=31d8fbb4e83f1; enc=s7EH5X4%2FGgqxsjL7VtaKey8phN9RXGhbGkKEyZpmr68mrE%2FnNHZdELKNJr6y22k%2FbxIXQYvdHcxSZpwQJWgPZQ%3D%3D; thw=cn; hng=CN%7Czh-CN%7CCNY%7C156; alitrackid=localhost; cna=nibkFqjYak0CAXeGZypvKU/b; sgcookie=EKd%2BRDRDPF%2FJG1XMg7Okz; unb=3981685338; uc3=vt3=F8dBxGR1T9WfwR2Of8Q%3D&lg2=URm48syIIVrSKA%3D%3D&nk2=3HLTelMH&id2=UNk%2FSaQ%2FYRgMow%3D%3D; csg=8d5f24da; lgc=%5Cu79D1%5Cu8FBEgg; cookie17=UNk%2FSaQ%2FYRgMow%3D%3D; dnk=%5Cu79D1%5Cu8FBEgg; skt=48c3c62d91f0d8b0; existShop=MTU4NzY2ODEzNA%3D%3D; uc4=nk4=0%403gaHD9GnaeORT92znufTRM8%3D&id4=0%40Ug41SKOjzmPgtWhljS4ncL0lYc5Y; tracknick=%5Cu79D1%5Cu8FBEgg; _cc_=W5iHLLyFfA%3D%3D; _l_g_=Ug%3D%3D; sg=g89; _nk_=%5Cu79D1%5Cu8FBEgg; cookie1=AiHMAoCGguEVoGsBWLpm8hBz2m%2FdybS6HlqpmxkrSsY%3D; tfstk=cTrhBBGvTfOfMsUQhpiQe6r-1jQAaSO-izzgbkWu4tTneiqZLsv67Eu9P1cQtuL5.; JSESSIONID=F301BA29D60E24F25DFC2CD06F7FAE99; lastalitrackid=login.taobao.com; mt=ci=7_1; v=0; l=eBQcoDLcQ2sy_ghQBO5whurza77t0QOf1sPzaNbMiIHca1RR6asdCNQcc5v6JdtjgtCX6eKPS7AArRdxbn4Nw14Ki2trCyCuwxvO",
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.113 Safari/537.36'
}
r = requests.get(url, timeout=30, headers=kv)
r.raise_for_status()
r.encoding = r.apparent_encoding
return r.text
except:
return "爬取失败"
def parsePage(glist, html):
'''
解析网页,搜索需要的信息
:param glist: 列表作为存储容器
:param html: 由getHTMLText()得到的
:return: 商品信息的列表
'''
try:
# 使用正则表达式提取信息
price_list = re.findall(r'\"view_price\"\:\"[\d\.]*\"', html)
name_list = re.findall(r'\"raw_title\"\:\".*?\"', html)
for i in range(len(price_list)):
price = eval(price_list[i].split(":")[1]) #eval()在此可以去掉""
name = eval(name_list[i].split(":")[1])
glist.append([price, name])
except:
print("解析失败")
def printGoodList(glist):
tplt = "{0:^4}\t{1:^6}\t{2:^10}"
print(tplt.format("序号", "商品价格", "商品名称"))
count = 0
for g in glist:
count = count + 1
print(tplt.format(count, g[0], g[1]))
# 根据页面url的变化寻找规律,构建爬取url
goods_name = "书包" # 搜索商品类型
start_url = "https://s.taobao.com/search?q=" + goods_name
info_list = []
page = 3 # 爬取页面数量
count = 0
for i in range(page):
count += 1
try:
url = start_url + "&s=" + str(44 * i)
html = getHTMLText(url) # 爬取url
parsePage(info_list, html) #解析HTML和爬取内容
print("\r爬取页面当前进度: {:.2f}%".format(count * 100 / page), end="") # 显示进度条
except:
continue
printGoodList(info_list)
结果展示
Xpath
Xpath的定义
Xpath是什么?
- Xpath即为XML路径语言(XML Path Language),它是一种用来确定XML文档中某部分位置的语言。
XML和Xpath
- XML文档是被作为节点树来对待的
- 在Xpath中,有七种类型的节点:元素、属性、文本、命名空间、处理指令、注释以及文档(根)节点
- Xpath使用路径表达式在XML文档中选取节点。节点是通过沿着路径选取的
HTML的转换
我们知道requests获得的都是HTML,但Xpath是专门处理XML的,因此我们得先对HTML进行转换。
from lxml import etree
req = requests.get(url)
html = req.text
tree = etree.HTML(html)
XML的读取
因为XML是被当作树来对待,是一层层的去寻找我们需要的内容的。
接下来,就是使用Xpath进行提取~
用户名称:tree.xpath(’//div[@class=“auth”]/a/text()’)
回复内容:tree.xpath(’//td[@class=“postbody”]’)
Xpath实战(丁香园)
目标网站:丁香园
from lxml import etree
import requests
url = "http://www.dxy.cn/bbs/thread/626626#626626"
tree = etree.HTML(requests.get(url).text)
user = tree.xpath('//div[@class="auth"]/a/text()')
content = tree.xpath('//td[@class="postbody"]')
results = []
for i in range(0, len(user)):
# print(user[i].strip()+":"+content[i].xpath('string(.)').strip())
# print("*"*80)
# 因为回复内容中有换行等标签,所以需要用string()来获取数据
results.append(user[i].strip() + ": " + content[i].xpath('string(.)').strip())
# 打印爬取的结果
for i,result in zip(range(0, len(user)),results):
print("user"+ str(i+1) + "-" + result)
print("*"*100)
结果展示:
Beautiful Soup
Beautiful Soup的定义
Beautiful Soup是什么?
- Beautiful Soup 是一个HTML/XML 的解析器,主要用于解析和提取 HTML/XML 数据。
Beautiful Soup直观了解
- 它虽然也可以用树去进行理解,但它和Xpath不一样,它可以认为是一种规则的数据结构,接下来我们直接使用它对HTML代码进行分析。
# 导入bs4库
from bs4 import BeautifulSoup
import requests # 抓取页面
r = requests.get('https://python123.io/ws/demo.html') # Demo网址
print(r.text) # 抓取的数据
可以看到,HTML代码整洁,一层层的,符合数据结构的思想~
# 解析HTML页面
soup = BeautifulSoup(demo, 'html.parser') # 抓取的页面数据;bs4的解析器
# 有层次感的输出解析后的HTML页面
print(soup.prettify())
HTML内容遍历
HTML基本格式:<>…</>构成了所属关系,形成了标签的树形结构
- 标签树的下行遍历
.contents 子节点的列表,将所有儿子节点存入列表
.children 子节点的迭代类型,与.contents类似,用于循环遍历儿子节点
.descendants 子孙节点的迭代类型,包含所有子孙节点,用于循环遍历
- 标签树的上行遍
.parent 节点的父亲标签
.parents 节点先辈标签的迭代类型,用于循环遍历先辈节点
- 标签树的平行遍历
.next_sibling 返回按照HTML文本顺序的下一个平行节点标签
.previous_sibling 返回按照HTML文本顺序的上一个平行节点标签
.next_siblings 迭代类型,返回按照HTML文本顺序的后续所有平行节点标签
.previous_siblings 迭代类型,返回按照HTML文本顺序的前续所有平行节点标签
HTML内容查找
- <>.find_all(name, attrs, recursive, string, **kwargs)
- 参数:
∙ name : 对标签名称的检索字符串
∙ attrs: 对标签属性值的检索字符串,可标注属性检索
∙ recursive: 是否对子孙全部检索,默认True
∙ string: <>…</>中字符串区域的检索字符串
以上内容都属于数据结构里的内容,一行一行代码输出更有利于学习该库~
实战(最好大学)
目标网站:最好大学网
# 导入库
import requests
from bs4 import BeautifulSoup
import bs4
def getHTMLText(url):
try:
r = requests.get(url, timeout=30)
r.raise_for_status()
r.encoding = r.apparent_encoding
return r.text
except:
return ""
def fillUnivList(ulist, html):
soup = BeautifulSoup(html, "html.parser")
for tr in soup.find('tbody').children:
if isinstance(tr, bs4.element.Tag):
tds = tr('td')
# 根据实际提取需要的内容,
ulist.append([tds[0].string, tds[1].string, tds[3].string])
# 对中英文混排输出问题进行优化:对format(),设定宽度和添加参数chr(12288)
def printUnivList(ulist, num=20):
tplt = "{0:^10}\t{1:{3}^10}\t{2:^10}"
print(tplt.format('排名', '学校名称', '总分', chr(12288)))
for i in range(num):
u = ulist[i]
print(tplt.format(u[0], u[1], u[2], chr(12288)))
u_info = [] # 存储爬取结果的容器
url = 'http://www.zuihaodaxue.cn/zuihaodaxuepaiming2019.html'
html = getHTMLText(url)
fillUnivList(u_info, html) # 爬取
printUnivList(u_info, num=30) # 打印输出30个信息
- getHTMLText:从网络上获取大学排名网页内容
- fillUnivList:提取网页内容中信息到合适的数据结构(二维数组)
- printUnivList:利用数据结构展示并输出结果
小结
三种不同方法的比较:
re | Xpath | bs4 | |
---|---|---|---|
安装 | 内置 | 第三方 | 第三方 |
语法 | 正则 | 路径匹配 | 面向对象 |
难度 | 困难 | 较困难 | 简单 |
效率 | 最高 | 适中 | 最低 |
- 大数据时代,效率为王,请选择正则表达式re。
- 后续再补充一个某度文库VIP文档爬取实战。
- 完。
转载:https://blog.csdn.net/weixin_43999137/article/details/105721010