学习了简单爬虫后的练手项目,爬取糗事百科的热门段子在控制台显示
修正
2016.7.25 段子正文去掉了原有的<– –>导致程序失效,新正则为
1 2 3
| '<div.*?author.*?">.*?<a.*?<h2>(.*?)</h2>.*?</a>.*?<div.*?' + 'content">(.*?)</div>(.*?)<div class="stats.*?' + 'class="number">(.*?)</i>.*?<span class="dash.*?class="number">(.*?)</i>'
|
说明
- 本文章为学习《Python爬虫实战一之爬取糗事百科段子》的二次实现,所以在文章布局和代码上都有参考
- 原文代码基于python 2.7版本,本文基于python3.5版本
- 本文对原博中已失效的正则部分进行更新,对功能、代码重构上的添加扩展和实现
目标
- 抓取糗事百科热门段子
- 过滤带有图片的段子
- 实现在控制台每按一次回车显示一个段子、作者、点赞数和评论数
实现步骤
1. 确定URL和抓取页面代码
首先打开http://www.qiushibaike.com ,
点击第二页观察网址,可以分析出8小时热门和24小时热门的网址分别对应http://www.qiushibaike.com/8hr/page/2和http://www.qiushibaike.com/hot/page/2,其中最后一位代表页数,通过改变最后一位可以获取到不同的页面。
尝试使用
1
| urllib.request.urlopen('http://www.qiushibaike.com/hot/page/2')
|
执行出错无法获取,添加header头试试
1 2 3 4 5
| url = 'http://www.qiushibaike.com/8hr/page/1' user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)' headers = {'User-Agent': user_agent} req = urllib.request.Request(url, headers=headers) response = urllib.request.urlopen(req)
|
成功获取到页面。
2. 提取一页中的所有段子
这一步需要构造网页解析器,本次将采用正则方式解析。
2.1 提取段子
通过审查元素看下段子的布局结构

根据页面框架构造出如下解析代码,其中 item[0]匹配段子正文, item[1]匹配作者, item[2]对于有图片的段子内有图片地址。由于本次输出在控制台下进行,所以item[2]仅用于过滤有图片的段子。
1 2 3 4 5 6 7 8
| pattern = re.compile('<div.*?author.*?">.*?<a.*?<h2>(.*?)</h2>.*?</a>.*?<div.*?' + 'content">(.*?)<!--.*?-->.*?</div>(.*?)<div class="stats.*?' + 'class="number">(.*?)</i>.*?<span class="dash.*?class="number">(.*?)</i>', re.S) items = re.findall(pattern, content) for item in items: haveImg = re.search("img", item[2]) if not haveImg: print('%s\n作者:%s %s好笑 %s评论\n' % (item[1], item[0], item[3], item[4]))
|
看下输出结果
1 2 3 4 5 6 7 8 9
| 妹妹:哥哥你的愿望是什么?<br/>哥哥:我们家族凋零,我要壮大我们的家族,娶上十七八个老婆,生上七八十个儿子。你呢?<br/>妹妹:我也想壮大家族,可是我就算娶了十七八个老公,也生不出七八十个孩子啊。
作者:翔→梦之堡 224好笑 4评论
N年前到一小商店,偶尔去买东西,店主和我都是面熟但算不上朋友,这是前提。有次买东西是女老板卖的,后因某种问题回去交涉,只有男老板在,我一急反复说是你妈妈卖的,这东西就是他家的。男老板一脸黑色,等事情交涉好走后,回头一想,他们是夫妻店呀,难快男店主不开心。真是罪过!
作者:转过街角 222好笑 1评论 。。。
|
2.2 提取最大页数
最大页数用于判断可以爬取的最大页数,对于超过最大页数的段子将采用循环方式重新从第一页开始获取。
看下页面结构

观察到最后一个class=”page-numbers”的span标签的值就是最大页数,这里通过贪婪模式来实现
1
| max_page_index = re.findall('<a.*<span class="page-numbers">\n(.*?)\n</span>', html_contents, re.S)
|
3. 完善细节交互,优化代码结构
进一步完善,结构细化到方法
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 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
|
import re import urllib.error import urllib.request
class QSBK: def __init__(self): """ 初始化方法 :return: """ self.url1 = 'http://www.qiushibaike.com/8hr/page/' self.url2 = 'http://www.qiushibaike.com/hot/page/' self.page_index = 1 self.max_page_index = 255 self.enable = True
def get_url(self): """ 生成待爬取地址 :return: 待爬取地址 """ index = self.page_index % self.max_page_index if index == 0: index = self.max_page_index url = self.url1 + str(index) self.page_index += 1 return url
@staticmethod def download_page(url): """ 根据传入的地址下载页码内容 :param url: :return:对应的html页面内容 """ try: user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)' headers = {'User-Agent': user_agent} req = urllib.request.Request(url, headers=headers) response = urllib.request.urlopen(req) return response.read().decode('utf-8') except urllib.error.URLError as e: if hasattr(e, 'reason'): print('Failed to reach a server.') print('Reason: ', e.reason) elif hasattr(e, 'code'): print("The server couldn't fulfill the request.") print('Error code: ', e.code) return None
def parse(self, html_contents): """ 解析传入的html内容 :param html_contents: :return: 当前页所有不带图片的段子 """ if self.max_page_index == 255: max_index = re.findall('<a.*<span class="page-numbers">\n(.*?)\n</span>', html_contents, re.S) if max_index: self.max_page_index = int(max_index[0])
stories = [] stories_pattern = re.compile('<div.*?author.*?">.*?<a.*?<h2>(.*?)</h2>.*?</a>.*?<div.*?' + 'content">(.*?)<!--.*?-->.*?</div>(.*?)<div class="stats.*?' + 'class="number">(.*?)</i>.*?<span class="dash.*?class="number">(.*?)</i>', re.S)
items = re.findall(stories_pattern, html_contents) for item in items: have_img = re.search("img", item[2]) if not have_img: text = re.sub('<br/>', "\n", item[1]) stories.append([text.strip(), item[0].strip(), item[3].strip(), item[4].strip()]) return stories
def print_story(self, stories): """ 输出一条段子回车继续显示 :param stories: :return: """ for story in stories: print('\n%s\n作者:%s %s好笑 %s评论' % (story[0], story[1], story[2], story[3])) input_a = input('回车继续输出段子,按Q结束\n') if input_a == 'Q' or input_a == 'q': self.enable = False return
def craw(self): """ 爬虫总调度方法 :return: """ while self.enable: url = self.get_url() html_contents = self.download_page(url) if not html_contents: print("Failed to load the page") return stories = self.parse(html_contents) self.print_story(stories)
if __name__ == '__main__': obj_spider = QSBK() obj_spider.craw()
|
运行结果
