项目目的:
- 爬取贴吧中所有帖子里面的图片
- 将爬取到的图片存储到名称为贴吧名称的文件夹中
项目环境
- python版本:python3.6
- 用到的库:requests、etree、unquote
- 浏览器:Chrome浏览器
一、页面分析
这里我以lol吧为例,进行分析。
右键点击‘检查’,鼠标移到某个帖子上,可以下图所示。
我们观察到在电脑网页中,百度贴吧html结构较为复杂,在这里我们可以用到一个小技巧。
分析网页结构小技巧
由于电脑版的网页html结构一般较为复杂,但是一般手机版的网页html结构会相对简单许多,所以我们可以在浏览器中,浏览手机版的网页来进行结构分析,这样可以有效地提高我们对网页结构的分析效率。
如何在浏览器中浏览手机版的网页?
我这里以谷歌浏览器为例进行演示。
在网页中点击‘右键——检查’
可以看到网页上方出现这行东西。点击最左边的‘Responsive’,会出现一些手机型号(如下图)。
下面点击iphone6,刷新后,依然‘右键——检查’与上面同样的帖子,这时候就会发现,网页的html结构比起全民的简单了许多。
但是别着急,还有跟简单的,下面点击‘Nokia Lumia 520’,(如果没有,可以点击列表最下方的Edit进行添加),这是一款非常老的诺基亚手机了,点击完,刷新后,就会能进入到最早的贴吧版本(满满的回忆有没有)。
大家也可以点击下面的地址去看一下:
https://tieba.baidu.com/mo/q—8D7E9D770BA62B40B3067027DC06EABD%3AFG%3D1–1-3-0----wapp_1554963358611_228/m?kw=lol&lp=5011&lm=&pn=0
还是右键点击刚刚那条帖子,这时会发现,这个版本的贴吧网页html结构是超级简单了。
然后点进去看一下,我们需要爬取的图片有没有
我们发现网页上的图片需要点击后才能进行查看,但是‘右键——检查’,我们需要的图片地址已经有了,红色框起来的便是。
既然有了简单的页面,我们当然就选择用简单的页面进行数据爬取工作啦。
二、开始编写爬虫程序
主要爬取逻辑
- 获取首页地址
- 发送请求,获取响应
进入贴吧首页 - 提取数据,获取下一页的url地址
3.1 提取列表页的url地址
3.2 请求列表页的url地址,提取下一页的地址
进入详情页
3.3 提取详情页的图片,提取下一页的地址
3.4 保存图片
3.5 请求下一页的地址,循环执行3.2-3.5 - 请求(列表页)下一页的url地址,循环执行2-4
项目源代码
import json
import os
from urllib.parse import unquote
import requests
from lxml import etree
class TiebaSpider:
def __init__(self, tieba_name):
'''
:param tieba_name: 贴吧名称
'''
self.tieba_name = tieba_name
self.part_url = 'https://tieba.baidu.com/mo/q---8D7E9D770BA62B40B3067027DC06EABD%3AFG%3D1--1-1-0--2--wapp_1554901733618_210' # 部分获取的地址缺少前面这部分
self.start_url = 'https://tieba.baidu.com/mo/q---8D7E9D770BA62B40B3067027DC06EABD:FG=1--1-1-0--2--wapp_1554901733618_210/m?kw=' + tieba_name + '&pn=0' # 首页地址
self.headers = {
'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Mobile Safari/537.36'}
if not os.path.isdir(tieba_name):
os.mkdir(tieba_name)
def parse_url(self, url):
'''
发送请求,获取响应
:param url: url地址
:return: html页面内容
'''
response = requests.get(url, headers=self.headers)
return response.content
def sava_img(self, img):
'''
保存图片
:param img:图片的下载链接
'''
file_path = self.tieba_name
img_name = img.split('/')[-1]
img = requests.get(img)
print('图片名称',img_name)
with open(os.path.join(file_path, img_name), 'wb') as f:
f.write(img.content)
def save_detail_img(self, detail_url):
'''
获取帖子图片并作保存处理
(这里用到递归,需要理解透彻)
:param detail_url: 详情页地址
:return: 图片总列表
'''
# 3.2 请求列表页的url地址,提取下一页的地址
detail_html_str = self.parse_url(detail_url)
detail_html = etree.HTML(detail_html_str)
# 3.3 提取详情页下一页的图片,提取下一页的地址
img_list = detail_html.xpath("//a[text()='图']/@href")
# 获取的网页上的图片地址:http://www.w3.org/1999/xhtml" href="http://c.hiphotos.baidu.com/forum/w%3D400%3Bq%3D80%3Bg%3D0/sign=5c474de444ed2e73fce9872cb73ad0b6/ec528e82b9014a909c3ee4dda7773912b31bee10.jpg?&src=http%3A%2F%2Fimgsrc.baidu.com%2Fforum%2Fpic%2Fitem%2Fec528e82b9014a909c3ee4dda7773912b31bee10.jpg
# 由于网页上的获取的图片地址经过编码压缩,所以下面进行解码操作
for img in img_list:
img = unquote(str(img))
# 保存该贴子的图片
self.sava_img(img)
print('保存成功')
# 3.4 请求详情页下一页的地址,进入循环3.2-3.4
detail_next_url = detail_html.xpath("//a[text()='下一页']/@href")
if len(detail_next_url) > 0:
detail_next_url = self.part_url + detail_next_url[0]
return self.save_detail_img(detail_next_url)
def run(self):
'''实现主要逻辑'''
next_url = self.start_url
while next_url is not None:
# 1.start_url
# 2.发送请求,获取响应
html_str = self.parse_url(next_url)
html = etree.HTML(html_str)
# 3.保存图片,提取下一页的url地址
# 3.1 提取列表页的url地址
# 3.2 请求列表页的url地址,提取下一页的地址
# 3.3 保存图片
# 3.4 提取详情页下一页的地址
# 3.5 请求详情页下一页的地址,进入循环3.2-3.4
div_list = html.xpath("//div[@class='i']") # 将每个帖子的div分块
for div in div_list: # 遍历每块帖子的div
href = self.part_url + div.xpath('./a/@href')[0] if len(div.xpath('./a/@href')) > 0 else None # 获取帖子详情页地址
self.save_detail_img(href) # 进入详情页并保存图片
# 3.5提取下一页url地址
next_url = self.part_url + html.xpath("//a[text()='下一页']/@href")[0] if len(html.xpath("//a[text()='下一页']/@href")) > 0 else None
# 4.请求(详情页)下一页的url地址,进入循环2-4
if __name__ == '__main__':
tieba_spider = TiebaSpider('lol')
tieba_spider.run()
运行结果
lol吧
李毅吧