python实战项目:爬取某小说网

项目文档:
  项目简介:
    爬取某小说网首页中的全部小说,并储存到数据库中
  项目版本 :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的爬取,避免程序的崩溃

项目结构:

image.png

ESBook 项目根目录
  bin 保存二进制可执行文件
  books 项目目录 存放py文件
  data 存放下载的小说封面
  docs 存储项目文档 使用sphinx生成的项目文档
  tests 测试目录
  README 项目说明
  requirements.txt 项目依赖第三方包
  setup.py python 安装脚本
image.png

Books目录结构
  Callblack 业务逻辑,网页的解析处理(这个就是抽象出来的解析功能)
  Logs 项目日志
  Util 通用性工具
  Bookconfig.py 项目配置文件
  link_crawler.py 链接爬取模块
  main.py 启动入口模块
image.png

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)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,400评论 25 707
  • # Python 资源大全中文版 我想很多程序员应该记得 GitHub 上有一个 Awesome - XXX 系列...
    aimaile阅读 26,436评论 6 428
  • 个人博客 随着移动互联网的发展,移动端iOS开发的技术选择也划分了好几个方向,有用 React-Native进行开...
    语歌阅读 312评论 1 1
  • 雨后的天空,是灰白的,照在发白的溢满皱褶的窗帘上。有老旧的阳光从某个缝里爬出来,沾在你清晨朦胧的睡影上。你...
    独木乔阅读 267评论 0 0
  • 喜欢你,不在需要任何理由。早起深深印在心里。毕业季不再是分手季。 遇见是大学一道五彩,有你未来多一些精彩。...
    祎爷阅读 212评论 0 0