工具:python 3.7.3 、Chrome 、PyCharm
爬取过程
一、安装库:
1.requests模块
在python2 和python3中通用,方法完全一样,简单易用,能够自动帮助我们解压(gzip压缩的等)网页内容。
response = requests.get(url, headers=headers)
2.json模块
实现json数据和python内建数据格式的相互转换
f.write(json.dumps(content,ensure_ascii=False))
3.re模块
Python提供re模块,包含所有正则表达式的功能,正则表达式是一种用来匹配字符串的强有力的武器。
re.findall(""total_page":(d+),",html_str)
4.LXML类库
lxml是一款高性能的 Python HTML/XML 解析器,我们可以利用XPath,来快速的定位特定元素以及获取节点信息。导入lxml的etree库,利用etree.HTML,将字符串转化为Element对象,Element对象具有xpath的方法。
html=etree.HTML(html_str)
二、爬取
1.分析
Chrome浏览器中打开百度贴吧进入任意贴吧,打开检查点击Network下的Preview寻找包含响应的url。依次查看左侧除图片和Js外的响应发现贴吧信息在以f?ie=utf-8开头的响应中。但是发现需要的信息都被注释起来了,尝试进入手机版网页获取响应,贴吧信息同样在以f?ie=utf-8开头的响应中。
观察响应为html,为静态网页,静态网页可以采取正则表达式、xpath等方法进行爬取。进行xpath爬取时借助Chrome插件 XPath Helper
改变贴吧名字和页码数观察url变化,发现改变参数kw的值可以切换贴吧,改变pn的值实现翻页,pn为从0开始公差为50的等差数列。
爬取任意贴吧,不清楚每个贴吧总页数,观察页面响应发现下一页url地址为#,提取不到,因而准备初始url地址,发送请求获取响应,提取数据和页面总数total_page并将数据保存,循环构造下一页url地址,发送请求获取响应,提取信息并保存的过程,直到pn大于(total_page-1)*50。
列表页可以获取到每个帖子的标题,也可以获取详情页url,获取详情页url后,发送请求获取响应,提取详情页图片,最后将提取数据结果放在字典中,以列表的形式返回。
每个帖子数据在li标签下,xpath提取帖子列表li_list,遍历列表获取每个帖子的数据
列表页标题内容在帖子列表li_list下的a标签下的span标签下,xpath提取文本数据
详情页url地址在帖子列表li_list下的a标签,xpath提取href属性。观察到详情页url地址不完整,需要补全
详情页图片在详情页响应span标签下的div标签,xpath提取data-url属性
页面总数total_page在列表页响应中,使用正则表达式提取
2.代码
import requests
from lxml import etree
import json
import re
class BaiduTieba:
def __init__(self,tiebaname):
self.tiebaname=tiebaname
self.start_url_temp="https://tiebac.baidu.com/mo/q/m?word="+tiebaname+"&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 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1"}
def parse_url(self,url):
response=requests.get(url,headers=self.headers)
# print(response.content.decode())
return response.content.decode()
def get_content_list(self, html_str):
html=etree.HTML(html_str)
li_list = html.xpath("//ul[@id='frslistcontent']/li[@class='tl_shadow tl_shadow_new ']")#分组
content_list=[]
for li in li_list:
item={}
item["title"]=li.xpath(".//a/div[@class='ti_title']/span/text()")[0] if len(li.xpath(".//a/div[@class='ti_title']/span/text()"))>0 else None
item["href"]=li.xpath(".//a/@href")[0] if len(li.xpath(".//a/@href"))>0 else None #详情页url
item["href"]="https://tieba.baidu.com"+item["href"] #详情页url地址不完整,补充
item["img_list"]=self.get_img_list(item["href"]) #将详情页图片放入字典中
content_list.append(item)
print(content_list)
total_page=int(re.findall(""total_page":(d+),",html_str)[0])
return content_list,total_page
def get_img_list(self,detail_url):
html_str=self.parse_url(detail_url)
html=etree.HTML(html_str)
img_list=html.xpath("//span[@class='wrap pbimgwapper']/div/@data-url")
return img_list
def save_content_list(self,content_list):
file_path=self.tiebaname+".txt" #以贴吧名保存文件
with open(file_path,"a",encoding="utf-8") as f:
for content in content_list:
f.write(json.dumps(content,ensure_ascii=False,indent=3))
f.write("n")
print("保存成功")
def run(self):
num=0
total_page=100
while num<=(total_page-1)*50:
url = self.start_url_temp.format(num)
html_str=self.parse_url(url)
content_list,total_page=self.get_content_list(html_str)
self.save_content_list(content_list)
num=num+50
if __name__=="__main__":
baidutieba=BaiduTieba("李毅")
baidutieba.run()
三、结果及出现问题
1.结果
2.问题
- 注意对照element和响应是否一致,不一致时以响应信息为准。
提取详情页图片时,element下图片在img标签下属性class为BDE_Image,需xpath提取src属性
而响应信息里图片在详情页响应span标签下的div标签,xpath提取data-url属性
如果按照element的信息提取,img_list将为空。