zoukankan      html  css  js  c++  java
  • 多线程爬取小说时如何保证章节的顺序

    前言


    爬取小说时,以每一个章节为一个线程进行爬取,如果不加以控制的话,保存的时候各个章节之间的顺序会乱掉。

    当然,这里说的是一本小说保存为单个txt文件,如果以每个章节为一个txt文件,自然不会存在这种情况。

    不仅仅是小说,一些其他的数据在多线程爬取时也有类似情况,比如:

    1. 漫画:漫画其实是由大量图片组成,一般一本漫画会保存为一个pdf文件,在此过程要保证图片的顺序。
    2. 视频:现在网络上的视频大部分是由多个ts文件拼合,最后保存为一个mp4文件,要保证ts文件的顺序。

    它们都有一个共同的特点,那就是:爬取目标时有多个链接,但保存后却只有一个文件,且无法在保存完毕后进行二次排序。

    单线程时,这一类文件的爬取不会出现任何问题;

    多线程时,因为爬取顺序的不确定性,导致保存的顺序紊乱。

     解决方案


    问题的根本原因在于:因为爬取和保存的一致性,混乱的爬取顺序使得保存顺序也变得混乱。

    解决方案:将爬取过程和保存过程分离,多线程爬取数据,但按顺序保存数据。

    比如一本小说,在爬取章节的过程中可以使用多线程,但不要在爬取之后立即保存,等待时机,精准写入。

    那这个时机是什么呢?

    可以在爬取章节的过程中,给每个章节一个带序号的标记,这个序号即小说章节的序号;保存时,从序号0开始保存,记录这一个序号,再检测当前爬取的章节中有没有比记录的序号刚好大于1的(大于1相当于下一个章节),有就写入,没有就等待。

    具体过程:

    1. 爬取目录页,抽取出所有的章节链接。
    2. 将所有待爬取的链接扔到一个队列里面去,同时给每个链接一个标记。
    3. 开5个线程,不断地从队列里面拿链接进行爬取。
    4. 单个章节爬取之后,让爬取这个链接的线程阻塞。
    5. 申明一个成员变量,表示保存的章节序号,从-1开始
    6. 当前线程的链接标记是否刚好比章节序号大于1,是就保存,不是就继续阻塞
    7. 因为是从队列中取数据,就能够保证这5个章节是还没有被爬取的前5个章节

    具体实现


    爬虫代码用的python实现,自定义的线程类,这里以http://www.booktxt.net/8_8455/爬取为例。

    代码如下:

    import requests
    from lxml import etree
    from threading import Thread
    from queue import Queue
    
    
    class MyThread(Thread):
        def __init__(self, q):
            Thread.__init__(self)
            self.q = q
    
        def run(self):
            global index
            while not self.q.empty():
                data = self.q.get()
                url = root + ''.join(data[1])
                response = requests.get(url, headers=headers)
                page = etree.HTML(response.content)
    
                chapter = page.xpath("//h1/text()")
                chapter = ''.join(chapter)
                print("爬取 -> %s" % chapter,index)
    
                content = page.xpath("//div[@id='content']/text()")
                content = '
    '.join(content)
                content = content.replace("xa0xa0xa0xa0", "	")
    
                # 如果当前标记比保存的小说章节序号大于1,阻塞
                while data[0] > index + 1:
                    pass
    
                # 刚好大于1时,通过,保存章节
                if data[0] == index + 1:
                    print("保存 -> %s" % chapter,index)
                    f.write('
    ' + chapter + '
    ')
                    f.write(content)
                    index += 1
    
    
    if __name__ == '__main__':
        root = "http://www.booktxt.net/8_8455/"
        headers = {
            'user-agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'
        }
        
        index = -1  # 章节标记,表示保存的章数
        
        response = requests.get(root, headers=headers)
        page = etree.HTML(response.content)
        title = ''.join(page.xpath("//h1/text()"))      # 小说名
        print(title)
    
        with open("%s.txt" % title, 'w', encoding='utf8') as f:
            f.write(title)      # 先写入小说名
            hrefs = page.xpath("//div[@id='list']/dl/dt[2]/following-sibling::dd/a/@href")
            q = Queue()
            for i,href in enumerate(hrefs):
                q.put((i,href))
    
            ts = []
            for i in range(5):
                t = MyThread(q)
                t.start()
                ts.append(t)
            for t in ts:
                t.join()

    代码整体上并不复杂,用的也是requests+lxml的经典组合,主要是队列数据写入和线程阻塞那里,能够理解整个也就不难了。

    总结


    该说的也都说了,最后说一句:

    如果是锻炼技术的话可以多线程爬这玩玩,但如果要写全网爬虫,还是写个单线程吧,尤其是在自己时间足够充裕的情况下,毕竟人家租个服务器也不容易。

  • 相关阅读:
    古文_硕鼠,原文及翻译
    使用php模拟post的几种方法
    alpha版、beta版、rc版的意思
    8007003Windows Update遇到未知错误
    树上10只鸟,开枪打死1只,还剩几只?
    [转]乐死我了,怎么样成为一个全栈程序员(Full Stack Developer),附我想专注的语言
    [转]Visual C++ RunTime的特征——非烫即屯
    斗破苍穹中的几个人物图片
    吐槽一下中国的大学的教材
    百度知道里关于C++的讨论
  • 原文地址:https://www.cnblogs.com/twoice/p/11405677.html
Copyright © 2011-2022 走看看