糗事百科段子采集的实例已经有很多前辈写过了,但年代久远,普遍基于python2.7,并且没有用到BeautifulSoup,而是自己写正则表达式进行匹配,因此,每次网站改版后,代码重构的工作量比较大。
本着造福群众(找项目练手)的原则,笔者写了一个基于python3、requests库和BeautifulSoup库的糗事百科段子采集爬虫。
1.先导入所需模块,再创建一个QSBK的类
import requests
from bs4 import BeautifulSoup
class QSBK(object):
def __init__(self):
# 页码
self.page_num = 1
# 存储采集到的段子
self.stories = {}
2.审查采集页面,确定采集策略
糗事百科热门段子任意一个页面的url是
https://www.qiushibaike.com/8hr/page/2/?s=4993718
?后面的内容先忽略,分析后,很明显2代表第二页,以此类推,可以找出url的规律。本实例使用requests库进行Http解析,不够需要注意一点,糗事百科会对use_agent为机器的访问者自动拦截,因此采集前需设置一下header中的use_agent。
# 获取糗事百科hot页面
def get_page(self):
# 初始化url
url = 'https://www.qiushibaike.com/8hr/page/' + str(self.page_num)
# 设置请求头,绕开网站的user_agent筛选
user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
headers = {'use_agent': user_agent}
# Get页面
res = requests.get(url, headers=headers)
# 设置页面编码
res.encoding = 'utf-8'
# 返回Beautiful Soup对象
return BeautifulSoup(res.text, 'html.parser')
观察发现,糗事百科段子分为两类:纯文字段子和图片+文字段子。采集图片并不复杂(python的urlretrieve)可以解决这个问题,但是在console展现图片则很是麻烦。因此,本项目只采集纯文字段子。
BeautifulSoup强大易用的tag解析是本项目的发动机。在用Chrome的审查元素功能查看网页结构后,便能释放bs的强大力量。
# 获取整个页面不带图片的段子
@staticmethod
def get_page_stories(bs_obj):
# 用于保存每个页面段子的数据结构
page_stories = []
# 利用Beautiful Soup采集页面段子
items = bs_obj.find('div', {'id': 'content-left'}). \
findAll('div', {'class': 'article block untagged mb15'})
for item in items:
# 剔除带图片的段子
if not item.find('div', {'class': 'thumb'}):
# author、content、vote_num、stats_num、god_cmt分别代表段子作者、段子正文、觉得段子好笑的人数、段子评论数、神评论
author = item.find('div', {'class': 'author clearfix'}).select('h2')[0].text
content = item.find('a', {'class': 'contentHerf'}). \
find('div', {'class': 'content'}).text
vote_num = item.find('div', {'class': 'stats'}). \
find('span', {'class': 'stats-vote'}).find('i').text
stats_num = item.find('div', {'class': 'stats'}). \
find('span', {'class': 'stats-comments'}).find('i').text
# igc = indexGodCmt
igc = item.find('a', {'class': 'indexGodCmt'})
if igc:
god_cmt = {'name': igc.find('div', {'class': 'cmtMain'}).find('span', {'class': 'cmt-name'}).text,
'cmt': igc.find('div', {'class': 'cmtMain'}).find('div', {'class': 'main-text'}).contents[0]}
else:
god_cmt = None
else:
continue
# 格式化段子
if god_cmt:
story = '糗友“%s”:\n%s\n%s人觉得好笑|评论数:%s\n“神评论”\n%s:%s' % \
(author.strip(), content.strip(), vote_num, stats_num,
god_cmt['name'].strip(), god_cmt['cmt'].strip())
else:
story = '糗友“%s”:\n%s\n%s人觉得好笑|评论数:%s' % \
(author.strip(), content.strip(), vote_num, stats_num)
# 将段子保存在page_stories中
page_stories.append(story)
# 返回page_stories
return page_stories
代码有点眼花缭乱,这应该是网页各式各样的标签的功劳。
3.储存段子、加载页面、展示结果
# 储存采集到的页面段子
def store_stories(self, page_stories):
self.stories[self.page_num] = page_stories
# 如果保存在stories中的段子页面少于2,则加载下一个页面的段子
def load_page(self):
if len(self.stories) < 2:
page_stories = QSBK.get_page_stories(self.get_page())
self.store_stories(page_stories)
self.page_num += 1
# 按一次回车,展示一个采集到的段子
def display(self):
active = True
while active:
self.load_page()
page_stories = self.stories.pop(self.page_num - 1)
for story in page_stories:
if input('按回车键显示一个段子(输入q退出):').lower() == 'q':
active = False
break
print(story)
4.效果演示
joke = QSBK()
joke.display()
最后非常感谢静觅博主(http://cuiqingcai.com/)的教程给予的帮助,本文有许多部分参考了其博文中的设计思路和方法。