python爬虫实战(1)--爬取糗事百科
这里利用正则表达式进行匹配,糗事百科是不需要登录的,所以也没必要用到Cookie,另外糗事百科有的段子是附图的,我们把图抓下来图片不便于显示,那么我们就尝试过滤掉有图的段子。
本篇目标
1.抓取糗事百科热门段子
2.过滤带有图片的段子
3.实现每按一次回车显示一个段子的发布页数,发布人,段子内容,点赞数
提取某一页的所有段子
由于网站经常更新,需要在更新后及时修改代码,如何修改在糗事百科网上按F12进行页面审查
# -*- coding:utf-8 -*- import urllib import urllib2 import re page = 1 url = 'http://www.qiushibaike.com/hot/page/' + str(page) user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)' headers = {'User-Agent': user_agent} try: request = urllib2.Request(url, headers=headers) response = urllib2.urlopen(request) content = response.read().decode('utf-8') pattern = re.compile('h2>(.*?)</h2.*?content".*?span>(.*?)</.*?!--.*?-->(.*?)</.*?number">(.*?)</i>',re.S) items = re.findall(pattern,content) for item in items: haveImg = re.search("img", item[2]) if not haveImg: print item[0], item[1], item[3] except urllib2.URLError, e: if hasattr(e, "code"): print e.code if hasattr(e, "reason"): print e.reason
1).*? 是一个固定的搭配,.和*代表可以匹配任意无限多个字符,加上?表示使用非贪婪模式进行匹配,也就是我们会尽可能短地做匹配,以后我们还会大量用到 .*? 的搭配。
2)(.*?)代表一个分组,在这个正则表达式中我们匹配了五个分组,在后面的遍历item中,item[0]就代表第一个(.*?)所指代的内容,item[1]就代表第二个(.*?)所指代的内容,以此类推。
3)re.S 标志代表在匹配时为点任意匹配模式,点 . 也可以代表换行符。
*这里在content,number后一定要加双引号”,<>与<>之间最好直接用.*?代替,直接写很容易出错
运行结果如下:
完善交互,设计面向对象模式
1 # -*- coding:utf-8 -*- 2 import urllib 3 import urllib2 4 import re 5 import thread 6 import time 7 8 # 糗事百科爬虫类 9 class QSBK: 10 # 初始化方法,定义一些变量 11 def __init__(self): 12 self.pageIndex = 1 13 self.user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)' 14 # 初始化headers 15 self.headers = {'User-Agent': self.user_agent} 16 # 存放段子的变量,每一个元素是每一页的段子们 17 self.stories = [] 18 # 存放程序是否继续运行的变量 19 self.enable = False 20 21 # 传入某一页的索引获得页面代码 22 def getPage(self, pageIndex): 23 try: 24 url = 'http://www.qiushibaike.com/hot/page/' + str(pageIndex) 25 # 构建请求的request 26 request = urllib2.Request(url, headers=self.headers) 27 # 利用urlopen获取页面代码 28 response = urllib2.urlopen(request) 29 # 将页面转化为UTF-8编码 30 pageCode = response.read().decode('utf-8') 31 return pageCode 32 33 except urllib2.URLError, e: 34 if hasattr(e, "reason"): 35 print u"连接糗事百科失败,错误原因", e.reason 36 return None 37 38 # 传入某一页代码,返回本页不带图片的段子列表 39 def getPageItems(self, pageIndex): 40 pageCode = self.getPage(pageIndex) 41 if not pageCode: 42 print "页面加载失败...." 43 return None 44 pattern = re.compile('h2>(.*?)</h2.*?content".*?span>(.*?)</.*?!--.*?-->(.*?)</.*?number">(.*?)</i>',re.S) 45 items = re.findall(pattern, pageCode) 46 # 用来存储每页的段子们 47 pageStories = [] 48 # 遍历正则表达式匹配的信息 49 for item in items: 50 # 是否含有图片 51 haveImg = re.search("img", item[2]) 52 # 如果不含有图片,把它加入list中 53 if not haveImg: 54 replaceBR = re.compile('<br/>') 55 text = re.sub(replaceBR, " ", item[1]) 56 # item[0]是一个段子的发布者,item[1]是内容,item[2]是图片,item[3]是点赞数 57 pageStories.append([item[0].strip(), text.strip(), item[3].strip()]) 58 return pageStories 59 60 # 加载并提取页面的内容,加入到列表中 61 def loadPage(self): 62 # 如果当前未看的页数少于2页,则加载新一页 63 if self.enable == True: 64 if len(self.stories) < 2: 65 # 获取新一页 66 pageStories = self.getPageItems(self.pageIndex) 67 # 将该页的段子存放到全局list中 68 if pageStories: 69 self.stories.append(pageStories) 70 # 获取完之后页码索引加一,表示下次读取下一页 71 self.pageIndex += 1 72 73 # 调用该方法,每次敲回车打印输出一个段子 74 def getOneStory(self, pageStories, page): 75 # 遍历一页的段子 76 for story in pageStories: 77 # 等待用户输入 78 input = raw_input() 79 # 每当输入回车一次,判断一下是否要加载新页面 80 self.loadPage() 81 # 如果输入Q则程序结束 82 if input == "Q": 83 self.enable = False 84 return 85 print u"第%d页 发布人:%s 赞:%s %s" % (page, story[0], story[2], story[1]) 86 87 # 开始方法 88 def start(self): 89 print u"正在读取糗事百科,按回车查看新段子,Q退出" 90 # 使变量为True,程序可以正常运行 91 self.enable = True 92 # 先加载一页内容 93 self.loadPage() 94 # 局部变量,控制当前读到了第几页 95 nowPage = 0 96 while self.enable: 97 if len(self.stories) > 0: 98 # 从全局list中获取一页的段子 99 pageStories = self.stories[0] 100 # 当前读到的页数加一 101 nowPage += 1 102 # 将全局list中第一个元素删除,因为已经取出 103 del self.stories[0] 104 # 输出该页的段子 105 self.getOneStory(pageStories, nowPage) 106 107 108 spider = QSBK() 109 spider.start()