爬虫基础篇之页面请求解析

篇幅有限

完整内容及源码关注公众号:ReverseCode,发送

CSS选择器

html中为指定元素指定显示效果,比如颜色,背景,字体等不同的属性,这些样式都是通过css选择器告诉浏览器指定样式风格。

表达式含义
#animal获取id为animal的所有元素
.animal获取class为animal的所有元素
a.active获取类为active的a标签
.animal > .pig获取类animal直接子元素中类为.pig的元素
.animal .pig获取类animal后代元素中类为.pig的元素
a[href*=”animal”]获取包含类animal的a元素
a[href^=”http”]获取href以http开头的a元素
a[href$=”gov.cn”]获取href以gov.cn结尾的a元素
div[class=”animal”][ctype=”pig”]获取多属性同时具备的元素
div > a:nth-child(2)获取div下的第二个a元素
.pig , .animal同时选择两个class的所有元素
p:nth-last-child(1)获取倒数第一个p元素
p:nth-child(even) p:nth-child(odd)获取奇数偶数节点
h3 + span获取h3 后面紧跟着的兄弟节点 span
h3 ~ span获取h3 后面所有的兄弟节点 span

实战

链家

目标抓取网站:https://su.lianjia.com/ershoufang/pg

抓取内容:分页抓取二手房的标题,地址,信息,关注量,标签,总价,单价等

分析

链家分析1

通过获取网页源代码发现所有的二手房信息都直接渲染在页面上,那么可以直接请求页面地址分析二手房源码后,通过parsel库parsel.Selector(html_data)转为我们可以使用选择器分析的对象。

通过css选择器.clear.LOGCLICKDATA拿到所有的二手房信息所在的li元素

链家分析2.png

在li元素下可以css选择器获取所有的.title a::text标题,.positionInfo a::text地址,.followInfo::text关注量等信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
selector = parsel.Selector(html_data)
lis = selector.css('.clear.LOGCLICKDATA')
for li in lis:
title = li.css('.title a::text').get() # 标题
address = li.css('.positionInfo a::text').getall() # 地址
address = ','.join(address)
houseInfo = li.css('.houseInfo::text').get() # 信息
followInfo = li.css('.followInfo::text').get() # 关注
tags = li.css('.tag span::text').get() # 标签
tags = ','.join(tags)
totalPrice = li.css('.totalPrice span::text').get() + '万' # 总价
unitePrice = li.css('.unitPrice span::text').get() # 单价
title_url = li.css('.title a::attr(href)').get() # 标题
print(title, address, houseInfo, followInfo, tags, totalPrice, unitePrice, title_url, sep="---")

爬取完成

点击下一页的时候,页面url添加了路径参数pg{},那么可以通过加该字段实现分页抓取。

链家爬取完成.png

猫眼电影

分析

目标抓取网站:https://maoyan.com/board

抓取内容:热映口碑榜的电影名,主演,上映时间等。

老规矩,查看网页源代码电影数据完整返回给前端,没有做异步请求。那么直接访问猫眼的热映口碑榜通过parsel库解析成Selector对象,开始利用css选择器分析页面字段。

通过控制台源码发现类.board-wrapper下dd元素包含了所有的电影信息,那么遍历其下的标签列表根据css选择器筛选拿到需要的数据即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
selector = parsel.Selector(html_data)
print(selector)
dds = selector.css('.board-wrapper dd')
for dd in dds:
title = dd.css('.name a::attr(title)').get()
star = dd.css('.star::text').get().strip()
releasetime = dd.css('.releasetime::text').get()
score = dd.css('.score i::text').getall()
score = ''.join(score)
print(title, star, releasetime, score)

with open('maoyan.csv', mode='a', encoding='utf-8', newline='') as f:
csv_write = csv.writer(f)
csv_write.writerow([title, star, releasetime, score])

爬取完成

猫眼爬取结果.png

喜马拉雅

分析

目标网站:https://www.ximalaya.com/xiangsheng/9723091

抓取内容:下载当前主题的所有页面的音频文件。

老规矩,查看网页源代码发现所有的音频标签会在当前页面ur后添加音频的id跳转到一个新的页面,如:https://www.ximalaya.com/xiangsheng/9723091/45982355

点击播放后,控制台的Media出现请求的音频地址,如:https://aod.cos.tx.xmcdn.com/group31/M01/36/04/wKgJSVmC6drBDNayAh_Q8WincwI414.m4a

喜马拉雅

通过控制台搜索音频关键字段,找到返回音频地址的请求https://www.ximalaya.com/revision/play/v1/audio?id=46106992&ptype=1

喜马拉雅音频源地址.png

该请求参数由音频id和ptype=1组成,通过css选择器.sound-list li.lF_ a::attr(href)分析列表页的音频的href拿到音频id,通过css选择器.sound-list li.lF_ a::attr(title)拿到音频标题。点击下一页发现只是在原url后添加p{page}字段,综上通过open函数写入音频文件完成下载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
titles = selector.css('.sound-list li.lF_ a::attr(title)').getall()
href = selector.css('.sound-list li.lF_ a::attr(href)').getall()
# zip() 可以讲两个列表进行打包, 遍历之后 是一个元组
data = zip(titles, href)
for index in data:
title = index[0]
mp3_id = index[1].split('/')[-1]
# f'{mp3_id}' '{}'.format(mp3_id) 字符串格式化方法
index_url = f'https://www.ximalaya.com/revision/play/v1/audio?id={mp3_id}&ptype=1'
response_1 = requests.get(url=index_url, headers=headers)
# 什么是json数据 字典嵌套字典 还嵌套一些列表
# json数据取值和字典取值方式是一样的 根据关键词提取内容 通俗的讲 就是根据左边的内容提取右边的内容
# print(response_1.text)
mp3_url = response_1.json()['data']['src']
print(title, mp3_url)
# 保存数据
# 保存数据: 如果是图片/音频/视频 等 都是要获取它的二进制数据,要以二进制的数据保存
mp3_content = requests.get(url=mp3_url).content
# 相对路径
with open('相声\\' + title + '.mp3', mode='wb') as f:
f.write(mp3_content)
print('正在保存: ', title)

爬取完成

喜马拉雅爬取完成.png

XPATH选择器

XPath (XML Path Language) 是由国际标准化组织W3C指定的,用来在 XML 和 HTML 文档中选择节点的语言。目前主流浏览器 (chrome、firefox,edge,safari) 都支持XPath语法,xpath有 1 和 2 两个版本,目前浏览器支持的是 xpath 1的语法,且比CSS选择器功能更强大。

表达式含义
/html/body/div选择根节点html下面的body下面的div元素,/从子节点找,//从所有子节点包括子节点的子节点中找
//div/*所有div节点下所有元素
//*[@id=’west’]id为west的元素
//select[@class=’single_choice’]class为single_choice的select元素
//p[@class=”capital huge-city”]多元素组合选择
//*[@multiple]具有multiple属性的元素
//*[contains(@style,’color’)]style包含color的元素
//*[starts-with(@style,’color’)]以style是color开头的元素,//*[ends-with(@style,’color’)]结尾元素
//div/p[2]所有div下的第二个p标签
//p[last()]最后一个p元素
//div/p[last()-2]所有div下倒数第三个p元素
//option[position()<=2]option类型的第1-2个元素
//*[@class=’multi_choice’]/*[position()>=last()-2]选择class属性为multi_choice的后3个子元素
//option|//h4所有的option元素 和所有的 h4 元素
//*[@id=’china’]/..选择 id 为 china 的节点的父节点
//*[@id=’china’]/../../..上上父节点
//*[@class=’single_choice’]/following-sibling::div选择后续节点中的div节点 等同于CSS选择器.single_choice ~ *
//[@class=’single_choice’]/preceding-sibling::前面兄弟节点

实战

新笔趣阁

分析

目标网站:http://www.xbiquge.la/10/10489/

抓取内容:抓取三寸人间所有章节的文章保存。

章节列表只有小说章节信息,点击每个章节跳转到章节页面,通常xpath表达式//div[@id="info"]/h1/text()拿到书籍名称,所有的章节都依赖于于id为list的div下的dl下的dd下的a标签的href属性跳转到章节页面。

三寸人间章节分析

拼接主域名http://www.xbiquge.la即可跳转到章节详情页面,通过xpath表达式//*[@id="content"]/text()拿到详情页面小说的完整内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 开文件流   打开一个文件 把我们数据写入到文件中去    a是追加写入 写入完第一章就继续追加写入第二章
with open(book_name + '.txt', 'a', encoding='utf-8')as f:
f.write(book_name+'\n')
# title 章节的名称 urls 每个章节的详情链接
# 遍历获取到该本书的每个章节和对应的内容详情链接 zip一次性遍历多个列表
for title,urls in zip(book_title,book_url):


c_url='http://www.xbiquge.la'+urls

print(title)
print(c_url)

# 异常处理
try: #捕捉异常
#参数1:单个章节的url:以获取到这个章节的小说内容的html源码 参数2:headers 参数3:请求等待时间3秒
titles_url = requests.get(c_url, headers=headers, timeout=3).content.decode('utf-8')
except: # 如果捕捉异常怎么办 请求失败那就再请求一遍
titles_url = requests.get(c_url, headers=headers).content.decode('utf-8')

# 那我们还差一个小说文本内容对不对 那每个章节链接我们有了
# 每个章节里面的内容是不是好解决 一样xpath语法给他获取下来
# 通过xpath获取到小说文本内容
book_content = etree.HTML(titles_url).xpath('//*[@id="content"]/text()')

f.write(title) # 先写入章节名称
f.write('\n')

# f.write不能够写列表,但可以写字符串格式(二进制)。。。 所以要for循环
for line in book_content:
f.write(line) # 再写入章节对应的内容
f.write('\n') # 每写完一章换行 一共1000多个章节

爬取完成

新笔趣阁抓取完成.png

其实很多情况下不需要自己去分析dom节点定位css或xpath表达式,chrome已经为我们集成了插件。

xpath和css获取方式.gif

JSON

很多情况页面不直接返回html或xml文本元素,或者这些文本分析起来很困难的情况下,可以通过控制台中的xhr模式抓取后端请求回来的json数据,直接解析json即可拿到想要的数据。

实战

拉勾

分析

目标网站:https://www.lagou.com/jobs/list_C%2B%2B?labelWords=&fromSearch=true&suginput=

抓取内容:抓取首页职位地址,公司名,规模等信息保存。

搜索C++后,打开控制台将结果中的带薪年假搜索拿到实际请求路径https://www.lagou.com/jobs/positionAjax.json?needAddtionalResult=false,该请求是post请求,参数如下

1
2
3
4
5
data = {
"first": "true",
"pn": "1",
"kd": "C++"
}

拉勾分析.png

通过控制台Preview分析返回的json数据,data['content']['positionResult']['result']即为职位信息

拉勾json.png

不过当我们直接请求时会报dtacess deny,可能对请求头中的参数做了校验。

1
2
3
4
5
6
7
8
Traceback (most recent call last):
File "F:/MyProject/CrawlerBase/lagou/lagou.py", line 21, in <module>
result = data['content']['positionResult']['result']
KeyError: 'content'
{'clientIp': '61.155.198.*',
'msg': 'dtaccess deny ',
'state': 2410,
'status': False}

我们将Cookie和User-Agent加入header后,即可以完整请求到json数据,进行数据分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
resp = requests.post(api_url, headers=headers)
pprint(resp.json())
data = resp.json()
result = data['content']['positionResult']['result']
# [print(r) for r in result]
for r in result:
d = {
'city': r['city'],
'companyFullName': r['companyFullName'],
'companySize': r['companySize'],
'education': r['education'],
'positionName': r['positionName'],
'salary': r['salary'],
'workYear': r['workYear']
}
with open('拉钩职位.csv',mode='a',encoding='utf-8') as f:
f.write(",".join(d.values()))
f.write("\n")

爬取完成

拉勾抓取.png

完整源码请关注微信公众号:ReverseCode,回复:爬虫基础

文章作者: J
文章链接: http://onejane.github.io/2021/03/30/爬虫基础篇之页面请求解析/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 万物皆可逆向
支付宝打赏
微信打赏