项目文档:
项目简介:
爬取某小说网首页中的全部小说,并储存到数据库中
项目版本 :python2.7.12
项目源码:
源码已上传 github: 源码github
项目总览:
1. 爬取小说首页中全部小说url
2. 爬取小说详情内容,章节url
3. 爬取章节信息
项目流程:
1. 爬取首页中的所有小说url
1. 获得首页html
2. 解析html
1) 获得小说url
3. 储存到数据库
1) 储存小说url
2. 爬取小说详情内容,章节url
1. 获得小说详情html
2. 解析html
1) 获得小说详情信息
2) 获得小说章节url
3. 储存到数据库
1) 储存小说详情信息
2) 储存章节url
3. 爬取章节信息
1. 获得章节html
2. 解析html
1) 获得章节信息
3. 储存到数据库
1) 储存章节信息
由上面流程可看出每个流程均可抽象为三个层次
1. 获得html
2. 解析html
3. 存储信息到数据库
因此可以抽象出三个功能:下载功能,解析功能,存储功能
项目中异常的处理:
项目中主要是 url的下载,解析中可能会出现一些异常,我们定义 出现此类异常则跑出ValueError异常,并跳过当前url,进行下一个url的爬取,避免程序的崩溃
项目结构:
ESBook 项目根目录
bin 保存二进制可执行文件
books 项目目录 存放py文件
data 存放下载的小说封面
docs 存储项目文档 使用sphinx生成的项目文档
tests 测试目录
README 项目说明
requirements.txt 项目依赖第三方包
setup.py python 安装脚本
Books目录结构
Callblack 业务逻辑,网页的解析处理(这个就是抽象出来的解析功能)
Logs 项目日志
Util 通用性工具
Bookconfig.py 项目配置文件
link_crawler.py 链接爬取模块
main.py 启动入口模块
uitl目录结构:
bookDB 数据库相关操作(抽象出来的存储功能)
Downloaders 下载功能的简单封装(抽象出来的下载功能)
Logger 日志功能
Logging.conf 日志配置文件
mongoQueue 基于mongoDB的url队列
Multiprocessloghandler 支持多进程的TimedRotatingFileHandler
Tools 工具模块
下面看一下项目配置文件Bookconfig.py
将项目配置统一到一个配置文件中,方便后续的更改
#coding=utf-8
"""
项目配置模块
数据库配置
mongodb数据库连接的创建
urls_table 集合的创建以及索引 book_url 的创建
urls_table 小说url表单,储存爬取的小说url
cha_urls_table 集合的创建以及索引 chapter_url的创建
cha_urls_table 章节url表单,储存爬取的小说章节url
books_table 集合的创建以及索引 book_id 的创建
books_table 小说表单,储存爬取的小说详情内容
chapters_table 集合的创建以及联合索引 book_id -- chapter_id 的创建
chapters_table 章节表单,储存爬取的小说章节内容
日志配置
consloe_logger 输出日志到控制台 info级别
error_logger 输出日志到日志文件 和 控制台 error级别
url状态 :数据库集合中url的状态
OUTSTANAING = 0 未处理
PROCESSING = 1 待处理
COMPLETE = 2 已处理
ERROR = 3 产生异常的url
"""
import pymongo
from books.util.logger import MyLogger
OUTSTANAING = 0 # 未处理
PROCESSING = 1 # 待处理
COMPLETE = 2 # 已处理
ERROR = 3 # 产生异常的url
#数据库连接创建
client = pymongo.MongoClient('localhost',27017)
book_db = client['book']
# 小说url表单,储存爬取的小说url
BOOK_URLS_TABLE = book_db['urls_table']
BOOK_URLS_FIELD = 'book_url'
BOOK_URLS_TABLE.create_index([(BOOK_URLS_FIELD,pymongo.DESCENDING)],unique=True)
# 章节url表单,储存爬取的小说章节url
CHA_URLS_TABLE = book_db['cha_urls_table']
CHA_URLS_FIELD = 'chapter_url'
CHA_URLS_TABLE.create_index([(CHA_URLS_FIELD,pymongo.DESCENDING)],unique=True)
# 小说表单,储存爬取的小说详情内容
BOOKS_TABLE = book_db['books_table']
BOOKS_FIELD = 'book_id'
BOOKS_TABLE.create_index([(BOOKS_FIELD,pymongo.DESCENDING)],unique=True)
# 章节表单,储存爬取的小说章节信息
# 小说id,章节id 联合索引
CHAPTERS_TABLE = book_db['chapters_table']
CHAPTERS_FIELD = 'chapter_id'
# 复合唯一索引,由book_id -- chapter_id 确认一条记录的唯一性
CHAPTERS_TABLE.create_index([(BOOKS_FIELD, pymongo.DESCENDING), (CHAPTERS_FIELD, pymongo.DESCENDING)], unique=True)
# 日志
# consloe_logger 输出日志到控制台 info级别
# error_logger 输出日志到日志文件 和 控制台 error级别
CONSOLE_LOGGER = MyLogger.getLogger('consolelogger')
ERROR_LOGGER = MyLogger.getLogger('errorlogger')
我们在看一下 link_crawler.py 模块
link_crawler.py 模块是项目的主体模块。
link_crawler.py 模块主要分三部分
1. 小说首页的爬取
2. 小说详情页的爬取
3. 小说章节页的爬取
小说首页的爬取
scrape_callblack即为解析小说首页的具体实现
def book_crawler(seen_url,delay=5,num_retries=3,encoding='utf-8',headers=None,scrape_callblack=None,cache=None):
"""小说首页爬取,获得首页中所有小说url,并存储到urls队列中
:param seen_url 小说主页url
:param delay 请求间隔时间
:param num_retries 下载错误时重试次数
:param encoding 网页编码
:param headers HTTP请求头信息
:param scrape_callblack 回调函数,用于处理具体业务逻辑
:param cache 数据缓存
:raise ValueError url解析失败则抛出
"""
# 待爬取URL
crawl_queue = seen_url if isinstance(seen_url,list) else [seen_url]
# urls队列,存储小说url
book_queue = MongoQueue(book_config.BOOK_URLS_TABLE,book_config.BOOK_URLS_FIELD)
# 下载功能
D = Downloader(delay=delay,num_retries=num_retries,headers=headers,encoding=encoding,cache=cache)
while crawl_queue:
try:
url = crawl_queue.pop()
try:
html = D(url)
# 解析功能--解析小说首页,返回首页中的所有小说urls
if scrape_callblack:
links = scrape_callblack(url,html)
# 将小说url添加到urls队列中
if links:
[book_queue.push(link) for link in links]
else:
#print '请添加回调处理函数'
break
except ValueError as e:
error_info = u'出现错误跳过当前url 信息为:%s %s ' % (e.message, url)
conlose_logger.error(error_info)
except KeyError as e:
conlose_logger.info(u'小说主页爬取已完成 ')
break
小说详情页的爬取
其中scrape_callblack为 解析小说详情页的具体实现,
小说urls则直接从小说urls队列中获取。
def book_detail_crwler(delay=5,num_retries=3,encoding='utf-8',headers=None,scrape_callblack=None,cache=None):
"""小说详情页爬取,将小说详情储存到数据库中,并将爬取到的章节url存储到urls队列中
:param delay 请求间隔时间
:param num_retries 下载错误时重试次数
:param encoding 网页编码
:param headers HTTP请求头信息
:param scrape_callblack 回调函数,用于处理具体业务逻辑
:param cache 数据缓存
:raise ValueError 详情页解析失败则抛出
"""
#小说url队列,获得小说url
book_queue = MongoQueue(book_config.BOOK_URLS_TABLE,book_config.BOOK_URLS_FIELD)
#章节url队列,储存爬取的章节url
chap_queue = MongoQueue(book_config.CHA_URLS_TABLE,book_config.CHA_URLS_FIELD)
D = Downloader(delay=delay, num_retries=num_retries, headers=headers, encoding=encoding, cache=cache)
while True:
try:
url = book_queue.pop()
try:
html = D(url)
# 解析功能--解析小说详情页并将小说详情储存到数据库中,返回其中的所有小说章节urls
if scrape_callblack:
chap_links = scrape_callblack(url,html)
# 将章节url添加到urls队列中
[chap_queue.push(link) for link in chap_links]
else:
#print '请添加回调处理函数'
break
except ValueError as e:
# 设置当前url状态为 ERROR
book_queue.set_error(url)
error_info = u'出现错误跳过当前url %s 信息为:%s ' % (url,e.message)
error_logger.error(error_info)
except KeyError as e:
#print '处理完成',e
conlose_logger.info(u'小说链接爬取已完成 ')
break
else:
#正常处理完成,设置url状态为 已处理
book_queue.complete(url)
小说章节页的爬取
scrape_callblack为解析章节页面的具体实现
def chpater_crwler(delay=5, num_retries=3, encoding='utf-8', headers=None, scrape_callblack=None, cache=None):
"""章节信息爬取,并储存到数据库
:param delay 请求间隔时间
:param num_retries 下载错误时重试次数
:param encoding 网页编码
:param headers HTTP请求头信息
:param scrape_callblack 回调函数,用于处理具体业务逻辑
:param cache 数据缓存
:raise ValueError 章节页解析失败则抛出
"""
# 章节urls队列
chap_queue = MongoQueue(book_config.CHA_URLS_TABLE, book_config.CHA_URLS_FIELD)
D = Downloader(delay=delay, num_retries=num_retries, headers=headers, encoding=encoding, cache=cache)
while True:
try:
url = chap_queue.pop()
try:
html = D(url)
# 解析功能--解析小说章节页,获得章节内容,并储存到数据库中
if scrape_callblack:
scrape_callblack(url,html)
else:
#print '请添加回调函数'
break
except ValueError as e:
# 设置当前url状态为 ERROR
chap_queue.set_error(url)
error_info = u'出现错误跳过当前url %s 信息为:%s ' % (url,e.message)
error_logger.error(error_info)
except KeyError as e:
conlose_logger.info(u'小说链接爬取已完成 ')
break
else:
# 正常处理完成,设置url状态为 已处理
chap_queue.complete(url)
main.py 模块为 程序入口,也是link_crawler.py的多进程版
book_detail_crawler()与chpater_crwler()是对小说详情页爬取与章节页爬取功能的包装,方便使用多进程时候的调用。
replace_img()
用来替换不正常的封面图片为默认封面图片
def book_detail_crawler():
"""小说详情页爬取"""
link_crawler.book_detail_crwler(headers={}, scrape_callblack=BookDetailCallblack())
def chpater_crwler():
"""章节页爬取"""
link_crawler.chpater_crwler(headers={}, scrape_callblack=ChapterSpiderCallblack())
def process_crawler(func):
"""启动进程"""
pool = multiprocessing.Pool(processes=8)
for i in range(8):
pool.apply_async(func)
pool.close()
pool.join()
def start():
"""启动爬虫"""
#爬取小说url
book_config.CONSOLE_LOGGER.info(u'开始 -- 爬取主页小说')
url = 'http://www.biquge.com/'
link_crawler.book_crawler(url,headers={},scrape_callblack=BookSpiderCallblack())
time.sleep(10)
#小说详情爬取
book_crawler = link_crawler.book_crawler
book_config.CONSOLE_LOGGER.info(u'开始 -- 小说详情爬取')
process_crawler(book_detail_crawler)
#替换不正常图片为默认封面图片
replace_img()
time.sleep(10)
#章节信息爬取
book_config.CONSOLE_LOGGER.info(u'开始 -- 章节详情爬取')
process_crawler(chpater_crwler)