0%

python爬虫实例之爬取糗事百科段子

爬什么

复习贴吧爬取的方法,熟练掌握数据提取方式。

怎么做

还是基本思路。url的规律很简单,总共就两页内容。

1
2
第1页:http://www.qiushibaike.cc/?&p=1
第2页:http://www.qiushibaike.cc/?&p=2

使用xpath提取各字段的值

分组

image.png

提取段子标题

image.png

提取作者名字

image.png

提取段子内容和发表时间

image.png

提取点赞数👍和点踩数👎

image.png

提取icon图片的url

image.png

提取发表图片的url

image.png

代码

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import requests
from lxml import etree
import json
from pprint import pprint

class qiubai:
def __init__(self):
self.url_temp = "http://www.qiushibaike.cc/?&p={}"
self.headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36"}

def get_url_list(self): #获取url列表
url_list = [self.url_temp.format(i+1) for i in range(2)]
# print(url_list)
return url_list

def parse_url(self,url):
response = requests.get(url,headers=self.headers)
html_str = response.content.decode()
return html_str

def parse_photo_url(self,url):
response = requests.get(url,headers=self.headers)
photo_byt = response.content
return photo_byt

def get_content_list(self,html_str): #<div>标签分组
content_list = []
html_element = etree.HTML(html_str)
div_list = html_element.xpath("//div[@class='block untagged']")
for div in div_list:
item = {}
item["title"] = div.xpath(".//a[contains(@id,'title')]/text()")[0] #提取段子标题
item["user"] = div.xpath(".//img/@alt")[0] #提取作者名字
item["content"] = div.xpath(".//div[@class='content']/p/text()|.//div[@class='content']/text()") #提取段子内容。这里有个知识点,xpath中使用竖线表示【或】,就是这两种匹配方式都对,用在提取的字段需要使用多种提取方式的场景
item["time"] = div.xpath(".//div[@class='content']/@title")[0] #提取发表时间
item["good"] = div.xpath(".//div[@class='up']/a/text()")[0] #提取点赞数👍
item["bad"] = div.xpath(".//div[@class='down']/a/text()")[0] #提取点踩数👎
item["icon_url"] = div.xpath(".//div[@style='float:right;']/img/@src")[0] #提取icon图片
item["photo_url"] = div.xpath(".//div[@class='thumb']/img/@src")[0] if len(div.xpath(".//div[@class='thumb']/img/@src"))>0 else None
content_list.append(item)
return content_list

def save_content(self,content_list,num):
file_path = "第{}页.json".format(num)
for content in content_list:
with open(file_path,"a",encoding="utf8") as f:
f.write(json.dumps(content,ensure_ascii=False,indent=2))

def save_icon(self,icon_byt,num):
file_path = "icon第{}张.jpg".format(num)
with open(file_path,"wb") as f:
f.write(icon_byt)

def save_photo(self,photo_byt,num):
file_path = "photo第{}张.jpg".format(num)
with open(file_path,"wb") as f:
f.write(photo_byt)

def run(self):
#1. 获取url
url_list = self.get_url_list()
icon_url_list = [] #初始化icon列表
photo_url_list = [] #初始化photo列表
content_num = 0
#2. 发送请求,获取响应
for url in url_list:
html_str = self.parse_url(url)
#3. 提取数据
content_list = self.get_content_list(html_str) #获取段子内容列表
page_num = url_list.index(url) #页码数(从0开始算)
self.save_content(content_list,page_num+1) #保存段子内容
for content in content_list:
content_num = content_list.index(content)
icon_url_list.append(content_list[content_num]["icon_url"]) #获取icon_url列表
# pprint(icon_url_list)
icon_num = len(icon_url_list) #icon图片的序号
icon_byt = self.parse_photo_url(icon_url_list[icon_num-1]) #发送请求,获取响应
self.save_icon(icon_byt,icon_num) #保存icon图片
while content_list[content_num]["photo_url"] is not None: #当photo_url字段的值不为空时,提取photo_url的值
photo_url_list.append(content_list[content_num]["photo_url"]) #获取photo_url列表
photo_num = len(photo_url_list) #发表图片的序号
photo_byt = self.parse_photo_url(photo_url_list[photo_num-1]) #发送请求,获取响应
# pprint(photo_url_list)
self.save_photo(photo_byt,photo_num) #保存发表图片
break
#4. 保存数据

if __name__ == '__main__':
my_qiubai = qiubai()
my_qiubai.run()

总结

和上一篇做法几乎一样,这篇是自我更正版本。上一篇获取icon图片和发表图片的代码中数学关系不对,由于爬的内容太多,没注意观察,其实爬的结果不对,图片的序号不正确。
还是不和数学计数的过程死磕,要把序号和图片url的顺序对应起来,可以换个方式来做。获取url列表的长度,这个url列表元素的个数减去1就是url列表元素对应的下标值。通过这个方法,url链接和序号就对应起来了,自己不会数数,就让len()函数帮忙数呗,这个函数总不会数错吧。其实就是很简单的一个思路,把列表的元素个数和元素下标值对应起来,这样就不用去想计数的过程了。就是偷懒才让程序更准确。
使用append方式添加列表元素,每增加一个列表元素,列表返回的元素个数就加一。描述起来很简单,就是要学会灵活使用。
拿不准xpath语句有没有写错,就在浏览器里面用xpath helper过一遍。否则调试程序的时候出现一堆xpath语法报错或者是提取不到数据,排查问题的过程就很费时间。

有个小问题

目前段位不够。用xpath怎么把这几条被<p>标签分隔的文本,合并显示,只输出一个结果。要正确地提取这段段子,并且使得输出结果是一个字符串,我现在只会用正则来做。
image.png

优化

找到方法啦,xpath直接打印<p>标签的内容就行了。这时候,每个<p>标签的文本都存在列表里面,一个文本段落就对应一个列表元素,取数据的时候取整个列表,就能够把整段内容获取下来。

  • 当字段值需要用到多种提取方式,就要使用竖线把多个提取规则写到xpath
    image.png
    image.png
    path规则:item["content"] = div.xpath(".//div[@class='content']/p/text()|.//div[@class='content']/text()")

    test

    I heard the echo, from the valleys and the heart
文本 & 图标 文本 & 大图标 (固定宽度)

标签页1文本内容

标签页2文本内容

标签页3文本内容

blah blah blah

点击显/隐内容

1
此处为要显示/隐的内容,亦可在此添加代码块,注意上下各空一行,

-------------本文结束感谢您的阅读-------------