这是写给自己玩的练习项目,从糗事百科中爬取段子放到微信公众号上去,这样我就能随时随地的看段子了,啊哈哈哈
项目结构
1.糗事百科爬虫:Pthon实现,MySQL做持久化存储
2.用免费的新浪SAE云搭建微信公众号的服务器
3.微信公众号部分
主要结构内容划分
糗事百科爬虫
技术实现:Python2.7
插件使用:re,urllib2,pymysql。没有使用beatifulsoap
实现思路:
1)起始:从糗事百科的“穿越”(http://www.qiushibaike.com/history/)栏,可以随机打开之前的某一天的糗事页面
2)查重:根据日期判断数据库中是否已经抓取过此页面
3)页面下载:如果没有抓去过,则下载当天前两个页面到页面队列中,等待解析
4)解析页面:糗事、页面url、日期、下一天的url(页面中随机展示)
5)持久化存储:将分离出来的逐个段子和其他信息,存储到mysql中
6)下一轮抓取:根据4中得到的下一页url,进入2流程(此步骤也可以是不断刷新“穿越”页面得到新的页面,即进入1流程)
7)数据输出:为了能在新浪云SAE中使用段子数据(免费云空间不能使用mysql),从mysql中导出糗事段子为文本文件
下面是爬虫中最主要的部分,流程控制的代码和页面解析的代码:
qiushi_history_main.py代码:控制整个流程
# -*- coding: utf-8 -*- import thread import time from qiushi_history_spider import html_parser from qiushi_history_spider import html_outputer from qiushi_history_spider import txt_outputer from qiushi_history_spider import page_manger from qiushi_history_spider import mysql_outputer from qiushi_history_spider import date_manager import urllib2 # ----------- 加载处理糗事百科 ----------- class Spider_Model: # 声明self:含有page pages enabled def __init__(self): self.pagemanger = page_manger.PageManager() self.htmlparser = html_parser.HtmlParser() # self.outputer=html_outputer.HtmlOutputer() self.txtoutputer = txt_outputer.TxtOutputer() self.mysqlOutputer = mysql_outputer.MysqlOutputer() self.datemanager = date_manager.DateManager() self.page = 1 self.pages = [] self.storys = [] self.enable = True self.maxPageNum = 400; def Start(self): self.enable = True page = self.page print u‘正在加载中请稍候......‘ # 新建一个线程在后台加载段子并存储 startUrl = ‘http://www.qiushibaike.com/history/‘ self.pagemanger.AddUrl(startUrl) connection = self.mysqlOutputer.open_conneciton() # 打开数据库连接 self.datemanager.set_conn(connection) thread.start_new_thread(self.pagemanger.LoadPage, ()) time.sleep(3) self.pages = self.pagemanger.pages # ----------- 加载处理糗事百科 ----------- while self.enable: # 存储并检验是否到达预订页数 if (page > self.maxPageNum): self.pagemanger.enable = False self.enable = False break # 如果self的page数组中存有元素 # 等待,防止pagemanager尚未读取完毕 time.sleep(2) if self.pages: if(len(self.pages)<=0): continue nowPage = self.pages[0] if(nowPage==None): del self.pages[0] continue del self.pages[0] #读取内容 self.storys, thisUrlCode, nextUrlCode, dateStr = self.htmlparser.GetStorys2(nowPage) # 判断是否重复 nodump = self.datemanager.no_duplicate(thisUrlCode) if (nodump): # region-----------保存当前日期的第一页--------------- self.datemanager.save_code(dateStr, thisUrlCode) # 当前这一步没有用,现在的页面是不停的刷新‘穿越’页得到的 # print ‘存入待读列表:‘,startUrl + nextUrlCode # self.pagemanger.AddUrl(startUrl + nextUrlCode) dateStoryDic = {‘story‘: self.storys, ‘urlcode‘: thisUrlCode, ‘date‘: dateStr, ‘page‘: 1} # print dateStoryDic self.mysqlOutputer.collect_data(dateStoryDic) self.PrintInfo(page, dateStr, thisUrlCode, nextUrlCode, 1) page += 1 # endregion # region-----------保存当前日期的第二页---------------- print "总页数:" + bytes(page), "日期:" + dateStr, "页码:" + bytes(2) print "当前页Code:",thisUrlCode+‘/page/2‘ secondPageUrl = startUrl+thisUrlCode+‘/page/2‘ secondPage=self.pagemanger.ReadPageFromUrl(secondPageUrl) if(secondPage==None): print "加载此页失败!" continue self.storys, thisUrlCode, nextUrlCode, dateStr = self.htmlparser.GetStorys2(secondPage) dateStoryDic = {‘story‘: self.storys, ‘urlcode‘: thisUrlCode, ‘date‘: dateStr, ‘page‘: 2} self.mysqlOutputer.collect_data(dateStoryDic) #page += 1 # endregion self.mysqlOutputer.output_database() else: self.enable = False break self.mysqlOutputer.close_conneciton() # 打印输出 抓取的总页数,日期字符串,当前code,下一错的,当前日期第几页 def PrintInfo(self, pagecount, datestr, thiscode, nextcode, pageIndex): print "总页数:" + bytes(pagecount), "日期:" + datestr, "页码:" + bytes(pageIndex) print "当前页Code:", thiscode #print "下一页Code:", nextcode # ----------- 程序的入口处 ----------- # 1从数据库中读取所有id? # 2每次从首页(history)读取 # 3首先获取当前页id,查重 # 4如无重复则解析 存储到数据库 # 5如果重复,直接读取下一页 # 6读取下一页,重复2-6 # Todolist: # 1.读取当天的第二页第三页 # 2.输出mysql数据库中的数据到文件 # 3.把datecode从数据库中读出到数组,以后查重更快 myModel = Spider_Model() myModel.Start() # 否则 python错误Unhandled exception in thread started by Error in sys.excepthook time.sleep(3)
html_parser.py 页面解析代码
# coding:utf8 import re class HtmlParser(object): #auther AllenRobin cnblogs.com/GISRSMAN/ # 将所有的段子都扣出来,添加到列表中并且返回列表 def __init__(self): self.storys=[] self.keywords=[] self.nextUrlCode=‘‘ #获得笑话正文、当前页Code、下一页Code def GetStorys(self,page): # print myPage unicodePage = page.decode("utf-8") # 找出所有class="content"的div标记 #re.S是任意匹配模式,也就是.可以匹配换行符 #myItems = re.findall(‘<div.*?class="content">(.*?)</div>‘,unicodePage,re.S) myItems = re.findall(‘<div.*?class="content">\n\n+<span>(.*?)</span>\n\n+</div>‘,unicodePage,re.S) thisUrlCode=re.findall(‘<link rel="canonical" href="http://www.qiushibaike.com/history/(.*?)/">‘,unicodePage,re.S)[0] nextUrlCode=re.findall(‘<a class="random" href="/history/(.*?)/".*?‘,unicodePage,re.S)[0] return myItems,thisUrlCode,nextUrlCode #获得笑话正文(作者赞数)、当前页Code、下一页Code def GetStorys2(self,pageContent): try: unicodePage= pageContent.decode("utf-8") pattern = re.compile(‘<div class="author clearfix">.*?<h2>(.*?)</h2>.*?<div.*?‘ +‘content">\n\n+<span>(.*?)</span>\n\n+</div>(.*?)<span class="stats-vote"><i class="number">(.*?)</i>‘,re.S) #items三个要素依次为用户名、段子内容、赞数 items = re.findall(pattern, pageContent) for item in items: #去除段子内容中的查看全文 item[1].replace("<span class=\"contentForAll\">查看全文","").replace("</span>","").replace("‘","\"") #除去含有图片的 haveImg = re.search("img", item[3]) if haveImg: print item del item #可以将这三个合并到上一个提高效率 thisUrlCode = re.findall(‘<link rel="canonical" href="http://www.qiushibaike.com/history/(.*?)/">‘, unicodePage, re.S)[0] nextUrlCode = re.findall(‘<a class="random" href="/history/(.*?)/".*?‘, unicodePage, re.S)[0] dateStrs = re.findall(‘<meta name="keywords" content="(.*?)" />‘, unicodePage, re.S)[0] return items,thisUrlCode,nextUrlCode,dateStrs except Exception, e: print Exception, ":", e
整个爬虫项目放到Github上吧,欢迎fork
GitHub地址:https://github.com/csdallen/qiushi_history_spider
参考资源: