项目结构
1.糗事百科爬虫:Pthon实现,MySQL做持久化存储
2.用免费的新浪SAE云搭建微信公众号的服务器
3.微信公众号部分
主要结构内容划分
糗事百科爬虫
技术实现:Python2.7
插件使用:re,urllib2,pymysql。没有使用beatifulsoap
实现思路:
1)起始:从糗事百科的“穿越”栏,可以随机打开之前的某一天的糗事页面
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
页面解析代码