爬什么
把百度贴吧【李毅吧】的内容爬取到本地并保存,需要提取用户名称、发表时间、评论数这几个字段,并且把用户头像和发表的图片保存到本地。
怎么做
- 获取url
- 发送请求,获取响应
- 提取数据
- 保存数据
获取url
观察可知,目标数据所在的url。
页码数和url的规律是这样:
1 2 3 4 5
| 第0页:https://tieba.baidu.com/mo/q/m?word=%E6%9D%8E%E6%AF%85&page_from_search=index&tn6=bdISP&tn4=bdKSW&tn7=bdPSB&lm=16842752&lp=6093&sub4=%E8%BF%9B%E5%90%A7&pn=0& 第1页:https://tieba.baidu.com/mo/q/m?word=%E6%9D%8E%E6%AF%85&page_from_search=index&tn6=bdISP&tn4=bdKSW&tn7=bdPSB&lm=16842752&lp=6093&sub4=%E8%BF%9B%E5%90%A7&pn=30& 第2页:https://tieba.baidu.com/mo/q/m?word=%E6%9D%8E%E6%AF%85&page_from_search=index&tn6=bdISP&tn4=bdKSW&tn7=bdPSB&lm=16842752&lp=6093&sub4=%E8%BF%9B%E5%90%A7&pn=60& …… 第i页:https://tieba.baidu.com/mo/q/m?word=%E6%9D%8E%E6%AF%85&page_from_search=index&tn6=bdISP&tn4=bdKSW&tn7=bdPSB&lm=16842752&lp=6093&sub4=%E8%BF%9B%E5%90%A7&pn={30i}&
|
看到总共是510699页。页码范围(1~510699)
发送请求,获取响应
遍历url列表,使用requests库发送请求。
提取数据
方法:观察可知,每个用户发表的信息都在相同属性的li标签。这种情况,提取数据最简单的方式就是xpath。
1 2 3
| 1. 获取分组的element对象(这里就是对提取的li标签内容做etree) 2. 对element对象做xpath,分别提取各个字段的值 3.(用户名称、发表时间、评论数这三个值可以直接用xpath提取出来,要获取到用户头像和发表的图片,必须先提取到图片对应的url地址之后,再发送请求,获取图片内容)
|
用户名称
发表时间
评论数
用户头像
发表的图片
保存数据
f.write保存到本地。注意,保存写入图片的时候,参数要加上b,只能以二进制方式写入,写入内容是bytes类型的response.content
代码
太不容易了———这个代码写了至少3个小时—–获取段子的内容并不难,主要是对获取到的url地址再次发起请求,获取响应。在写入图片的时候没有使用bytes类型,花费了很多时间排查这个错误——
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 91 92 93 94 95 96 97 98 99 100
| import requests from lxml import etree import json from pprint import pprint
class tieba: def __init__(self): self.url_temp = "https://tieba.baidu.com/mo/q/m?word=%E6%9D%8E%E6%AF%85&page_from_search=index&tn6=bdISP&tn4=bdKSW&tn7=bdPSB&lm=16842752&lp=6093&sub4=%E8%BF%9B%E5%90%A7&pn={}&" self.headers = {"User-Agent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Mobile Safari/537.36"}
def get_url_list(self): url_list = [self.url_temp.format(i*30) for i in range(2)] return url_list
def parse_url(self,url): html_str = requests.get(url,headers=self.headers) html_str = html_str.content.decode() return html_str
def get_content_list(self,html_str): html_str = etree.HTML(html_str) grp_str_list = html_str.xpath("//li[@class='tl_shadow tl_shadow_new ']") content_list = [] for grp_str in grp_str_list: item = {} item["user_name"] = grp_str.xpath(".//span[contains(@class,'ti_author')]/text()")[0] item["time"] = grp_str.xpath(".//span[@class='ti_time']/text()")[0] item["comments"] = grp_str.xpath(".//span[@class='btn_icon']/text()")[0] item["content"] = grp_str.xpath(".//div[@class='ti_title']/span/text()") item["icon_url"] = grp_str.xpath(".//div[@class='ti_avatar']/img/@src")[0] item["photo_url"] = grp_str.xpath(".//div[@class='medias_wrap ordinary_thread clearfix']//img/@data-url")[0] if len(grp_str.xpath(".//div[@class='medias_wrap ordinary_thread clearfix']//img/@data-url"))>0 else None content_list.append(item) return content_list
def save_content_list(self,content_list,page_num): file_path = "第{}页.json".format(page_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,page_num): file_path = "icon第{}张.jpg".format(page_num) with open(file_path,"wb") as f: f.write(icon_byt) def save_photo(self,photo_byt,page_num): file_path = "photo第{}张.jpg".format(page_num) with open(file_path,"wb") as f: f.write(photo_byt) def parse_photo_url(self,url): photo_str = requests.get(url,headers=self.headers) photo_str = photo_str.content return photo_str def run(self): url_list = self.get_url_list() icon_url_list = [] photo_url_list = [] for url in url_list: html_str = self.parse_url(url) content_list = self.get_content_list(html_str) page_num = url_list.index(url)+1 self.save_content_list(content_list,page_num) for content in content_list: i = 1 icon_url_list = [] photo_url_list = [] icon_num = content_list.index(content) icon_url_list.append(content_list[icon_num]["icon_url"]) icon_byt = self.parse_photo_url(icon_url_list[0]) photo_num = page_num*i+icon_num self.save_icon(icon_byt,photo_num) while content_list[icon_num]["photo_url"] is not None: photo_url_list.append(content_list[icon_num]["photo_url"]) photo_byt = self.parse_photo_url(photo_url_list[0]) self.save_photo(photo_byt,photo_num) break i = i +1 if __name__ == '__main__': my_tieba = tieba() my_tieba.run()
|
总结
主要还是对数据类型不熟悉。这个练习,涉及了对列表和字典的操作。
怎么构造字典,怎么构造列表。这里都用了append
的方式将新增的数据添加到空列表。在使用之前,必须记得初始化列表。其中有个错误,就是因为没有初始化列表photo_url_list,也是花了很多时间才找到原因。目标数据的字段多于两个,就可以把数据构造成字典,append到空列表中存储。
写入图片,直接使用response.content
这种bytes类型的数据,不能再decode。
第一次使用while。while就是永远循环,直到条件不成立,就结束循环,这个代码要搭配break,否则程序卡死动不了。
用xpath提取数据,不要忘记考虑这个点,有些分组可能提取不到某些字段,这个时候,就要把相应字段的值设置为None
,保持字典的完整性。
item["photo_url"] = grp_str.xpath(".//div[@class='medias_wrap ordinary_thread clearfix']//img/@data-url")[0] if len(grp_str.xpath(".//div[@class='medias_wrap ordinary_thread clearfix']//img/@data-url"))>0 else None
这个叫做三元表达式。和列表推导式很类似,就是先看条件,条件满足,就赋值给左边,否则赋值到右边。
然后就是对于代码里面设置的循环次数这类数学问题,很生疏。虽然日常生活中的加减乘除都没有大问题,代码里面的数学问题,就算只有小学数学的难度,也要把人搞昏……真的挺不喜欢i这个变量。在学校里面学c语言的时候,就是从i的各种加减乘除开始的,什么i++,++i,整得头痛,编程的兴趣就是这么耗尽的,用代码解决纯数学问题太太太枯燥了。
还有一个点需要特别注意,在浏览器使用xpath helper的时候,要注意检查响应的内容和页面的element内容有没有差别。今天就被坑了……element里面的代码和url的响应内容有些些差别,这一点细微的差别,导致的结果就是xpath提取不到数据。所以,用xpath helper工具虽然方便,还是要多留心,最好是以响应数据为准,做xpath的时候优先看响应数据。
更正
代码的计数方式不对。改为:
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 91 92 93 94 95
| import requests from lxml import etree import json from pprint import pprint
class tieba: def __init__(self): self.url_temp = "https://tieba.baidu.com/mo/q/m?word=%E6%9D%8E%E6%AF%85&page_from_search=index&tn6=bdISP&tn4=bdKSW&tn7=bdPSB&lm=16842752&lp=6093&sub4=%E8%BF%9B%E5%90%A7&pn={}&" self.headers = {"User-Agent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Mobile Safari/537.36"}
def get_url_list(self): url_list = [self.url_temp.format(i*30) for i in range(2)] return url_list
def parse_url(self,url): html_str = requests.get(url,headers=self.headers) html_str = html_str.content.decode() return html_str
def get_content_list(self,html_str): html_str = etree.HTML(html_str) grp_str_list = html_str.xpath("//li[@class='tl_shadow tl_shadow_new ']") content_list = [] for grp_str in grp_str_list: item = {} item["user_name"] = grp_str.xpath(".//span[contains(@class,'ti_author')]/text()")[0] item["time"] = grp_str.xpath(".//span[@class='ti_time']/text()")[0] item["comments"] = grp_str.xpath(".//span[@class='btn_icon']/text()")[0] item["content"] = grp_str.xpath(".//div[@class='ti_title']/span/text()") item["icon_url"] = grp_str.xpath(".//div[@class='ti_avatar']/img/@src")[0] item["photo_url"] = grp_str.xpath(".//div[@class='medias_wrap ordinary_thread clearfix']//img/@data-url")[0] if len(grp_str.xpath(".//div[@class='medias_wrap ordinary_thread clearfix']//img/@data-url"))>0 else None content_list.append(item) return content_list
def save_content_list(self,content_list,page_num): file_path = "第{}页.json".format(page_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,page_num): file_path = "icon第{}张.jpg".format(page_num) with open(file_path,"wb") as f: f.write(icon_byt) def save_photo(self,photo_byt,page_num): file_path = "photo第{}张.jpg".format(page_num) with open(file_path,"wb") as f: f.write(photo_byt) def parse_photo_url(self,url): photo_str = requests.get(url,headers=self.headers) photo_str = photo_str.content return photo_str def run(self): url_list = self.get_url_list() icon_url_list = [] photo_url_list = [] for url in url_list: html_str = self.parse_url(url) content_list = self.get_content_list(html_str) page_num = url_list.index(url)+1 self.save_content_list(content_list,page_num) for content in content_list: content_num = content_list.index(content) icon_url_list.append(content_list[content_num]["icon_url"]) icon_num = len(icon_url_list) icon_byt = self.parse_photo_url(icon_url_list[icon_num-1]) self.save_icon(icon_byt,icon_num) while content_list[content_num]["photo_url"] is not None: photo_url_list.append(content_list[content_num]["photo_url"]) photo_num = len(photo_url_list) photo_byt = self.parse_photo_url(photo_url_list[photo_num-1]) self.save_photo(photo_byt,photo_num) break if __name__ == '__main__': my_tieba = tieba() my_tieba.run()
|
好像每一次的疑问,都一定能找到答案。真的很巧。