1.步骤解析
- 首先要能链接网页url,得到糗百网页的HTML全部代码(我们在上一篇博文已经做到了)
- 对糗事百科网页进行正则表达式分析
- 编写python3.6爬虫代码
我们在上一篇博文中,已经成功抓取到了糗百网页的全部HTML代码,上篇博文我们使用了简单的正则表达式进行糗百图片识别。我们这一篇博文,将使用更加复杂的正则表达式来抓取我们需要的内容。下面正式开始:
2.糗事百科网页正则表达式分析
我这里找到一个工具,来进行正则表达式的测试。说实话,我被正则表达式搞的也头大,之前也没接触过,也是刚刚学的。下面是我用的正则表达式在线测试工具网页:http://tool.chinaz.com/regex/?qq-pf-to=pcqq.c2c大家在自己编正则表达式的时候,可以用这个网页进行测试(注意:这个工具很智障,或者说因为“.”的正则表达式默认情况下,不能表示“\n”换行,,所以要测试时,不能整段正则表达式一起测试……),下面我们开始分析糗百热门页段子的HTML代码的正则表达式:
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 |
<div class="article block untagged mb15 typs_recent" id='qiushi_tag_120678301'> <div class="author clearfix"> <a href="/users/38917837/" target="_blank" rel="nofollow" style="height: 35px" onclick="_hmt.push(['_trackEvent','web-list-author-img','chick'])"> <img src="//pic.qiushibaike.com/system/avtnew/3891/38917837/thumb/2018061609170438.JPEG?imageView2/1/w/90/h/90" alt="骑着电车撵老鼠"> </a> <a href="/users/38917837/" target="_blank" onclick="_hmt.push(['_trackEvent','web-list-author-text','chick'])"> <h2> 骑着电车撵老鼠 </h2> </a> <div class="articleGender manIcon">0</div> </div> <a href="/article/120678301" target="_blank" class="contentHerf" onclick="_hmt.push(['_trackEvent','web-list-content','chick'])"> <div class="content"> <span> 上大学的时候,依旧身材瘦小<br/>~隔~<br/>有一天鼻炎犯了,去学校旁边的药店买药<br/>有鼻炎药吗?<br/>啥!避孕药!!!<br/>药店瞬间沸腾了<br/>那笑声,那表情,我。。。 </span> </div> </a> <!-- 图片或gif --> <div class="stats"> <!-- 笑脸、评论数等 --> <span class="stats-vote"><i class="number">508</i> 好笑</span> <span class="stats-comments"> <span class="dash"> · </span> <a href="/article/120678301" data-share="/article/120678301" id="c-120678301" class="qiushi_comments" target="_blank" onclick="_hmt.push(['_trackEvent','web-list-comment','chick'])"> <i class="number">4</i> 评论 </a> </span> </div> <div id="qiushi_counts_120678301" class="stats-buttons bar clearfix"> <ul class="clearfix"> <li id="vote-up-120678301" class="up"> <a href="javascript:voting(120678301,1)" class="voting" data-article="120678301" id="up-120678301" rel="nofollow" onclick="_hmt.push(['_trackEvent','web-list-funny','chick'])"> <i></i> <span class="number hidden">511</span> </a> </li> <li id="vote-dn-120678301" class="down"> <a href="javascript:voting(120678301,-1)" class="voting" data-article="120678301" id="dn-120678301" rel="nofollow" onclick="_hmt.push(['_trackEvent','web-list-cry','chick'])"> <i></i> <span class="number hidden">-3</span> </a> </li> <li class="comments"> <a href="/article/120678301" id="c-120678301" class="qiushi_comments" target="_blank" onclick="_hmt.push(['_trackEvent','web-list-comment01','chick'])"> <i></i> </a> </li> </ul> </div> <div class="single-share"> <a class="share-wechat" data-type="wechat" title="分享到微信" rel="nofollow">微信</a> <a class="share-qq" data-type="qq" title="分享到QQ" rel="nofollow">QQ</a> <a class="share-qzone" data-type="qzone" title="分享到QQ空间" rel="nofollow">QQ空间</a> <a class="share-weibo" data-type="weibo" title="分享到微博" rel="nofollow">微博</a> </div> <div class="single-clear"></div> </div> |
这个就是我们要抓取的段子的HTML代码,我们要对这个进行正则表达式分析:
我们要抓取:1发布人,2段子的全部信息的部分地址, 3发布内容, 4发布图片, 5点赞数。打开上面的正则表达式在线测试工具链接:(注意:这个工具很智障,或者说因为“.”的正则表达式语法默认情况下,不能表示“\n”换行,,所以要测试时,不能整段正则表达式一起测试……只能单独测试一小部分正则内容)
比如我们测试段子内容的正则表达式:
我们打开之前写的博文:Python3.6爬虫入门自学教程之九:python中的正则表达式学习看一下正则表达式相关知识:
<span>(.*?)</span>,这个正则表达式实际上用到的语法,就是上面四个图。
首先括号括起来的部分,将作为一个分组,保存到一个list中。其次“.”这个语法,可以在DOTALL模式下,也就是传入re.compile()这个方法的参数flag = re.S,就是DOTALL模式。然后是“”语法,其匹配的是”.”0次或者无限次。然后就是“?”搭配,让这个变成非贪婪模式。
下面是贪婪模式和非贪婪模式简介:在我们上一篇博文中有详细介绍:Python3.6爬虫入门自学教程之九:python中的正则表达式学习
当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符。以这个表达式为例:a.*b,它将会匹配最长的以a开始,以b结束的字符串。如果用它来搜索aabab的话,它会匹配整个字符串aabab。这被称为贪婪匹配。
有时,我们更需要懒惰匹配,也就是匹配尽可能少的字符。前面给出的限定符都可以被转化为懒惰匹配模式,只要在它后面加上一个问号?。这样.*?就意味着匹配任意数量的重复,但是在能使整个匹配成功的前提下使用最少的重复。现在看看懒惰版的例子吧:
a.*?b匹配最短的,以a开始,以b结束的字符串。如果把它应用于aabab的话,它会匹配aab(第一到第三个字符)和ab(第四到第五个字符)。
为什么第一个匹配是aab(第一到第三个字符)而不是ab(第二到第三个字符)?简单地说,因为正则表达式有另一条规则,比懒惰/贪婪规则的优先级更高:最先开始的匹配拥有最高的优先权——The match that begins earliest wins。
其他地方的正则表达式分析和上面差不多,下面是所有的正则表达式:
1 2 3 4 5 |
pattern = re.compile('<div class="article.*?<h2>(.*?)</h2>' + '.*?<a href="(.*?)"' + '.*?<span>(.*?)</span>' + '.*?<!-- 图片或gif -->(.*?)<div class="stats">' + '.*?<span class="stats-vote"><i class="number">(.*?)</i>', re.S) |
这四个括号括起来的内容,分别代表1发布人,2段子的全部信息的部分地址, 3发布内容, 4发布图片, 5点赞数
#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 |
import urllib.request import http.client import ssl import gzip import zlib import re ssl._create_default_https_context = ssl._create_unverified_context # gzip压缩 def gzip_decompress(data): try: # 尝试解压 print('正在使用gzip解压.....') data = gzip.decompress(data) print('gzip解压完毕!') except: print('未经gzip压缩, 无需解压') return data # deflate压缩算法 def deflate_decompress(data): try: print('正在使用deflate解压.....') return zlib.decompress(data, -zlib.MAX_WBITS) print('deflate解压完毕!') except zlib.error: return zlib.decompress(data) # 封装头信息,伪装成浏览器 header = { 'Connection': 'Keep-Alive', 'Accept-Language': 'zh-CN,zh;q=0.8', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36', 'Accept-Encoding': 'gzip, deflate', 'X-Requested-With': 'XMLHttpRequest', } url = "https://www.qiushibaike.com/8hr/page/1/" try: # 使用包含header的信息,进行请求 request = urllib.request.Request(url, headers=header) # 通过urlopen打开包含header的url链接,用来获取数据 response = urllib.request.urlopen(request) except urllib.error.HTTPError as e: print(e.code) except urllib.error.URLError as e: print(e.reason) except http.client.error as e: print(e) else: # 读取返回的结果 content = response.read() # 用于判断是何种压缩算法,如果是gzip则调用gzip算法 encoding = response.info().get('Content-Encoding') # 判断使用的是不是gzip压缩算法 if encoding == 'gzip': content = gzip_decompress(content).decode(encoding='utf-8', errors='strict') # deflate很少有人在用了,老网站可能用,这里也判断一下 elif encoding == 'deflate': content = deflate_decompress(content) pattern = re.compile('<div class="article.*?<h2>(.*?)</h2>' + '.*?<a href="(.*?)"' + '.*?<span>(.*?)</span>' + '.*?<!-- 图片或gif -->(.*?)<div class="stats">' + '.*?<span class="stats-vote"><i class="number">(.*?)</i>', re.S) items = re.finditer(pattern, content) for item in items: # 有的段子在首页看不全,需要点击查看原文,这些不全的段子我们选择忽略 if not re.search("查看全文", item.group()): result = re.sub("<br/>", "\n", item.group(3)) print(item.group(1).strip(), result.strip(), item.group(5).strip()+"\r\n\r\n") # 没有显示全部内容,通过item[1](保存的段子链接)发起请求访问段子的全部内容 |
Python strip() 方法用于移除字符串头尾指定的字符(默认为空格或换行符)或字符序列。
注意:该方法只能删除开头或是结尾的字符,不能删除中间部分的字符。
运行结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
D:\python\python.exe E:/技术学习/Python代码/15.爬取糗事百科小项目/抓糗百文章.py 正在使用gzip解压..... gzip解压完毕! 萌宝兜兜儿 周末闲着没事,在房间看糗百……看着看着,听到客厅的老爸老妈吵了起来:你能干啥?看你生的闺女,跟傻子似的,我爸也不甘示弱的说到:你闺女好到哪里去了?跟个白痴似的,我在房间想到,哈哈,这俩人吵架都这么逗……半个小时我才反应过来……慢着,我家不就我这么一个闺女么[诶~][诶~][诶~][诶~][诶~] 1491 隔壁老张学英语 以前我是不在微信运动朋友圈拿个第一名不睡觉,现在是早上不发个早起打卡截图不睡觉。 369 因吹丝顶 妈妈说我是在垃圾桶捡来的 我问过了,他们不丢这个人 208 1992猴灬子 碰到一傻X,开车别我骂人就算了。把我逼停后,下来两人要打我。当我从车内拿出棒球棍的时候他俩说这件事就算了吧。我觉得他俩这个逼装的我给十分[擦汗][擦汗][擦汗]真事不编,刚发生。。。 684 |
好了,是不是很炫酷,我们已经可以爬下来糗百的段子了。不过这个代码还比较low,下面我们对它进行升级,优化一下我们的代码。
4.升级版本的爬虫
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 |
import urllib.request import urllib.error import re __author__ = "wz" class QSBK: def __init__(self): # 表示下一次要读取的页面 self.index = 1 self.header = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.96 Safari/537.36" } self.url = "https://www.qiushibaike.com/hot/page/" self.mainUrl = "https://www.qiushibaike.com" # 每个元素存储一页提取好的段子 self.stories = [] # enable = True 时获取下一页的段子 self.enable = False # 获得页面内容 def getPage(self, index=None, contentUrl=None): try: response = None if index: request = urllib.request.Request(self.url + str(index), headers=self.header) response = urllib.request.urlopen(request) elif contentUrl: request = urllib.request.Request(self.mainUrl + contentUrl, headers=self.header) response = urllib.request.urlopen(request) return response.read().decode() except urllib.error.URLError as e: print("getPage失败") if hasattr(e, "code"): print(e.code) if hasattr(e, "reason"): print(e.reason) return None # 提取每一页中不带图片的段子 def getPageItems(self, index): content = self.getPage(index=index) # 分组信息:1发布人,2段子的全部信息的部分地址, 3发布内容, 4发布图片, 5点赞数 pattern = re.compile('<div class="article.*?<h2>(.*?)</h2>' + '.*?<a href="(.*?)"' + '.*?<span>(.*?)</span>' + '.*?<!-- 图片或gif -->(.*?)<div class="stats">' + '.*?<span class="stats-vote"><i class="number">(.*?)</i>', re.S) items = re.finditer(pattern, content) pageItems = [] # 一个item代表一个段子 for item in items: # 如果段子中没有图片,保存段子 if not re.search("img", item.group(4)): # 如果已经显示了段子的全部内容 # print(item.group()) if not re.search("查看全文", item.group()): result = re.sub("<br/>", "\n", item.group(3)) pageItems.append([item.group(1).strip(), result.strip(), item.group(5).strip()]) # 没有显示全部内容,通过item[1]发起请求访问段子的全部内容 else: contentForAll = self.getPage(contentUrl=item.group(2)) # ForAll页面的正则表达式是之前的不太相同 patternForAll = re.compile('''<div class="article.*?<h2>(.*?)</h2>''' + '''.*?<div class="content">(.*?)</div>''' + '''.*?<span class="stats-vote"><i class="number">(.*?)</i>''', re.S) itemForAll = re.findall(patternForAll, contentForAll) result = re.sub("<br/>", "\n", itemForAll[0][1]) pageItems.append([itemForAll[0][0].strip(), result.strip(), itemForAll[0][2].strip()]) return pageItems # 加载并提取页面的内容,加入到列表中 def loadPage(self): if self.enable: # 如果当前未看的页数少于2页,则加载新一页 if len(self.stories) < 2: pageStories = self.getPageItems(self.index) if pageStories: self.stories.append(pageStories) self.index += 1 # 获取一个段子 def getOneStory(self, pageStories, page): for story in pageStories: # python3之后raw_input已经被抛弃 receive = input() self.loadPage() if receive == "Q" or receive == "q": self.enable = False return print("当前第:%s页\n发布人:%s\n内容:%s\n点赞数:%s\n" % (page, story[0], story[1], story[2])) # 开始 def start(self): self.enable = True self.loadPage() nowPage = 0 while self.enable: if len(self.stories) > 0: pageStories = self.stories[0] nowPage += 1 del self.stories[0] self.getOneStory(pageStories, nowPage) if __name__ == "__main__": spider = QSBK() spider.start() |
这个代码是 github上一个网友“wz”写的,这里直接引用他的代码。封装的很棒,引入了python类,自己封装了一下。很好的代码,挺清晰的。
转载请注明:燕骏博客 » Python3.6爬虫入门教程练手小项目之二:爬取糗事百科网站的段子.作者.评论数.赞数
赞赏作者微信赞赏支付宝赞赏