这是一篇关于自己写贴吧图片下载器的总结。
起初是室友说要做一个贴吧的信息收集爬虫,于是打算先从简单一点的开始,先做一个贴吧的图片下载吧,之后再把获取图片改成获取背的内容不就好了嘛😄,然后就开始写了这个爬虫。现在这个Python脚本主要用到了下面的一些东西。
用到的Python模块
- requests(Python下的HTTP客户端实现)
- BeautifulSoup(一个很方便的html语法分析工具)
- colorama(在控制台输出彩色的
- threading(我用到了多线程的一些东西)
- signal(Python下和信号相关的东西)
- OptionParser(很方便的命令行参数配置工具)
- threadpool(一个菊苣写的Python线程池实现)
原理的部分
其实思路很简单,随便找一个帖子,比如这个http://tieba.baidu.com/p/2545680017,这是一个图楼,然后就需要分析页面的HTM代码内容,我用的是Chrome浏览器,那就右键选择一个楼层的图片,审查元素
,然后发现HTML代码中是这样写的:
<img class="BDE_Image" src="http://imgsrc.baidu.com/forum/w%3D580/sign=198d73cadc54564ee565e43183dc9cde/857f1f1f95cad1c8f75fb3fe7e3e6709c83d5130.jpg" pic_ext="jpeg" width="550" height="600">
可以看到里面src
部分直接是图片的地址,再观察楼层图片和其他无关紧要的图片(比如层主头像、签名档)的写法区别,发现楼层图片都会有一个BDE_Image
class,那么这就好办了,等会直接筛选出来就好。
然后翻到帖子的第二页,发现URL变成了http://tieba.baidu.com/p/2545680017?pn=2,多了一个?pn=2
,这个pn
参数也就表示了当前查看的页数位置,那么我们只需要知道帖子总共有多少页,对pn
做一个遍历就可以获得一个帖子中所有页面了,现在需要知道怎么获取帖子的总页面数。直接来到帖子的最底部,看到了这样的内容62回复贴,共3页
,对应的HTML代码为:
<li class="l_reply_num" style="margin-left:8px"><span class="red" style="margin-right:3px">62</span>回复贴,共<span class="red">3</span>页</li>
特征很明显,有一个叫做l_reply_num
的class,总回帖数和总页数都各用一个sapn标签包起来了,等会直接过滤出来就可以。
现在对网页部分的分析算是结束了,到了代码实现的部分了。
这里贴出部分代码
# 用requests模块直接获取网页的HTML内容
getparams = {'pn': 1}
result_ = requests.get('http://tieba.baidu.com/p/2545680017?', params=getparams).content
# 用BeautifulSoup模块做HTML内容的分析,获取总页数
soup = BeautifulSoup(result_, 'lxml')
page_list = soup.find('li', attrs={'class': 'l_reply_num'})
max_page_number = int(page_list.contents[2].string) # 总页数
# 在pn处做一个遍历,获取到帖子的所有页面
for pageNum in range(start, end):
getparams = {'pn': pageNum}
html_cont = requests.get(self.pURL, params=getparams).content
soup = BeautifulSoup(html_cont, 'lxml')
# 用BeautifulSoup模块做HTML内容的分析,获取到所有楼层图片的下载地址
# 并发送到下载队列
for item in soup.findAll('img', attrs={'class': "BDE_Image"}):
srcurl = item.get('src') # 图片地址
if not self._is_Exit:
self.downloader.add_task(srcurl)
# 下载器里用threadpool模块做多线程的并发下载,这样速度比单线程快很多
def add_task(self, url):
with self.lock:
self._is_alive = True
reqs = threadpool.makeRequests(self._download, [url, ], self._callback)
[self.threadPool.putRequest(req, timeout=1) for req in reqs]
# 直接用requests模块完成图片的下载,并保存到文件
def _download(self, url):
if self.is_alive():
r = requests.request('GET', url)
if r:
filename = os.path.join(self.save_directory, os.path.basename(url)) # 拼接文件名
if not os.path.exists(filename):
with open(filename, 'wb') as f:
f.write(r.content) # 保存图片
return 1
else:
return -1
else:
return None
速度还不错
END
到这里其实关键的部分也就结束了,剩下的一些微小的工作,比如输出统计结果(下了多少图,用了多少时间,支持设置保存目录。。。),各位看官感兴趣的话可以直接去我的Github上看源码。