• python爬虫实战——小说爬取


    python爬虫实战——小说爬取

    基于requests库和lxml库编写的爬虫,目标小说网站域名http://www.365kk.cc/,类似的小说网站殊途同归,均可采用本文方法爬取。

    目标网站传送门

    本文的目标书籍《我的师兄实在太稳健了》

    “渡劫只有九成八的把握,和送死有什么区别?”

    基本思路

    网络爬虫的工作实际上主要分为三个部分:

    • 获取网页内容,通过requests库实现;
    • 解析网页内容,得到其中我们想要的部分,通过lxml库实现;
    • 将解析出的内容储存到文本文档中;

    接下来看看我们的需求是什么:

    • 获取目标书籍的基本信息,包括书籍的书名、作者、最近更新时间和简介——这些信息应该都在同一个页面中获取,即目标书籍的主页;

    • 获取目标书籍每一章节的标题和内容——不同章节在不同的页面,不同页面之间可以通过下一页按序跳转;

    • 正文部分的存储格式应便于阅读,不能把所有文字都堆积在一起,也不能包括除了正文之外的其他无关内容;

    因此,我们首先尝试请求书籍的主页,获取基本信息,借此学习请求和解析网页内容的基本方法;紧接着再从书籍的第一章开始,不断地请求“下一页”,直到爬取所有内容,并将它们以合适的格式储存在文本文档中。


    首页爬取与解析

    首页链接:http://www.365kk.cc/255/255036/

    目标内容在网页中的位置

    首先,我们观察一下该网站不同的书籍主页:

    可以看出:不同书籍的主页具有非常相似的结构——标题、作者、最近更新时间、最新章节和正文都在相同的位置上。

    如何表征这一位置?这就需要借助python爬虫中常用的网页解析方法——XPath语法

    以书名为例,在你的浏览器(以Google Chrome为例)中选中书名,点击右键 -> 检查:

    可以看出,浏览器下方弹出了一个窗口,这里显示的就是该页面的源代码,我们选中的内容位于一个

    标签中。我们点击右键 -> 复制 -> 复制 XPath,即可得到书名的XPath路径,也就是书名在网页中的位置。

    从书籍的首页中,我们需要获取的信息主要包括:

    • 书名
    • 作者
    • 最后更新时间
    • 简介

    按照上述方法,分别获取它们的XPath路径,依次如下:

    /html/body/div[4]/div[1]/div/div/div[2]/div[1]/h1/text()
    /html/body/div[4]/div[1]/div/div/div[2]/div[1]/div/p[1]/text()
    /html/body/div[4]/div[1]/div/div/div[2]/div[1]/div/p[5]/text()
    /html/body/div[4]/div[1]/div/div/div[2]/div[2]/text()
    
    • 1
    • 2
    • 3
    • 4

    /text() 表示获取文本

    现在,我们得到了想要的内容在网页中的位置,只要请求到网页内容,就可以获取指定位置的内容了。


    请求网页内容

    我们使用比较基础的python爬虫网页请求方法:使用 requests 库直接请求。

    这里涉及到了简单的反爬虫知识:在请求网页时,我们需要将我们的爬虫伪装成浏览器,具体通过添加请求头 headers 实现。

    请求头以字典的形式创建,可以包括很多内容,这里只设置四个字段:User-Agent, Cookie, HostConection

    headers= {
        'User-Agent': '...',  # 你的浏览器User-Agent
        'Cookie': '...',      # 你的浏览器Cookie
        'Host': 'www.365kk.cc',
        'Connection': 'keep-alive'
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    既然是为了伪装成浏览器,相关字段的内容当然要从浏览器中获取。

    在刚才打开的页面中,我们点击 网络(英文版是Network),刷新页面,找到其中的第一个文件 255036/ ,打开 标头 -> 请求标头 ,即可得到我们想要的字段数据,如下图所示。

    这里的文件255036/实际上就是小说主页的文件,可以看出,主页的链接为http://www.365kk.cc/255/255036/,链接后缀刚好与文件名一致。

    使用 requests 库的 get 方法请求网站内容,将其解码为文本形式,输出结果验证,完整的代码如下:

    import requests
    
    
    # 请求头,添加你的浏览器信息后才可以正常运行
    headers= {
        'User-Agent': '...',
        'Cookie': '...',
        'Host': 'www.365kk.cc',
        'Connection': 'keep-alive'
    }
    
    # 小说主页
    main_url = "http://www.365kk.cc/255/255036/"
    
    # 使用get方法请求网页
    main_resp = requests.get(main_url, headers=headers)
    
    # 将网页内容按utf-8规范解码为文本形式
    main_text = main_resp.content.decode('utf-8')
    
    print(main_text)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    运行代码前,需要向 headers 中填入你自己浏览器的信息。

    输出结果如下:

    可以看出,我们成功请求到了网站内容,接下来只需对其进行解析,即可得到我们想要的部分。


    解析网页内容

    我们使用 lxml 库来解析网页内容,具体方法为将文本形式的网页内容创建为可解析的元素,再按照XPath路径访问其中的内容,代码如下:

    import requests
    from lxml import etree
    
    
    # 请求头
    headers= {
        'User-Agent': '...',
        'Cookie': '...',
        'Host': 'www.365kk.cc',
        'Connection': 'keep-alive'
    }
    
    # 小说主页
    main_url = "http://www.365kk.cc/255/255036/"
    
    # 使用get方法请求网页
    main_resp = requests.get(main_url, headers=headers)
    
    # 将网页内容按utf-8规范解码为文本形式
    main_text = main_resp.content.decode('utf-8')
    
    # 将文本内容创建为可解析元素
    main_html = etree.HTML(main_text)
    
    # 依次获取书籍的标题、作者、最近更新时间和简介
    # main_html.xpath返回的是列表,因此需要加一个[0]来表示列表中的首个元素
    bookTitle = main_html.xpath('/html/body/div[4]/div[1]/div/div/div[2]/div[1]/h1/text()')[0]
    author = main_html.xpath('/html/body/div[4]/div[1]/div/div/div[2]/div[1]/div/p[1]/text()')[0]
    update = main_html.xpath('/html/body/div[4]/div[1]/div/div/div[2]/div[1]/div/p[5]/text()')[0]
    introduction = main_html.xpath('/html/body/div[4]/div[1]/div/div/div[2]/div[2]/text()')[0]
    
    # 输出结果以验证
    print(bookTitle)
    print(author)
    print(update)
    print(introduction)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    输出结果如下:

    至此,我们已经学会了基本的网页请求方法,并学会了如何获取目标页面中的特定内容。


    正文爬取与解析

    接下来,我们开始爬取正文。首先尝试获取单个页面的数据,再尝试设计一个循环,依次获取所有正文数据。

    单个页面数据的获取

    以第一章为例,链接:http://www.365kk.cc/255/255036/4147599.html,获取章节标题和正文的XPath路径如下:

    //*[@id="container"]/div/div/div[2]/h1/text()
    //*[@id="content"]/text()
    
    • 1
    • 2

    按照与上文一致的方法请求并解析网页内容,代码如下:

    import requests
    from lxml import etree
    
    
    # 请求头
    headers= {
        'User-Agent': '...',
        'Cookie': '...',
        'Host': 'www.365kk.cc',
        'Connection': 'keep-alive'
    }
    
    # ...
    # 上一部分的代码
    # ...
    
    # 当前页面链接
    url = 'http://www.365kk.cc/255/255036/4147599.html'
    
    resp = requests.get(url, headers)
    text = resp.content.decode('utf-8')
    html = etree.HTML(text)
    
    title = html.xpath('//*[@id="container"]/div/div/div[2]/h1/text()')[0]
    contents = html.xpath('//*[@id="content"]/text()')
    
    print(title)
    for content in contents:
        print(content)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    输出结果如下:

    可以看出,我们成功获取了小说第一章第一页的标题和正文部分,接下来我们将它储存在一个txt文本文档中,文档命名为之前获取的书名 bookTitle.txt,完整的代码如下:

    import requests
    from lxml import etree
    
    
    # 请求头
    headers= {
        'User-Agent': '...',
        'Cookie': '...',
        'Host': 'www.365kk.cc',
        'Connection': 'keep-alive'
    }
    
    # 小说主页
    main_url = "http://www.365kk.cc/255/255036/"
    
    # 使用get方法请求网页
    main_resp = requests.get(main_url, headers=headers)
    
    # 将网页内容按utf-8规范解码为文本形式
    main_text = main_resp.content.decode('utf-8')
    
    # 将文本内容创建为可解析元素
    main_html = etree.HTML(main_text)
    
    bookTitle = main_html.xpath('/html/body/div[4]/div[1]/div/div/div[2]/div[1]/h1/text()')[0]
    author = main_html.xpath('/html/body/div[4]/div[1]/div/div/div[2]/div[1]/div/p[1]/text()')[0]
    update = main_html.xpath('/html/body/div[4]/div[1]/div/div/div[2]/div[1]/div/p[5]/text()')[0]
    introduction = main_html.xpath('/html/body/div[4]/div[1]/div/div/div[2]/div[2]/text()')[0]
    
    # 当前页面链接
    url = 'http://www.365kk.cc/255/255036/4147599.html'
    
    resp = requests.get(url, headers)
    text = resp.content.decode('utf-8')
    html = etree.HTML(text)
    
    title = html.xpath('//*[@id="container"]/div/div/div[2]/h1/text()')[0]
    contents = html.xpath('//*[@id="content"]/text()')
    
    with open(bookTitle + '.txt', 'w', encoding='utf-8') as f:
        f.write(title)
        for content in contents:
            f.write(content)
        f.close()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    运行结束后,可以看到在代码文件的同路径中,已经生成了一个文本文档:

    打开该文档,可以看到储存好的内容:

    储存好的内容中,大段的文字堆积在一起,而原文确实有着段落的区分,因此我们在储存文件时,每储存一段,就写入两个换行符 \n,使格式更便于阅读:

    with open(bookTitle + '.txt', 'w', encoding='utf-8') as f:
        f.write(title)
        for content in contents:
            f.write(content)
            f.write('\n\n')
        f.close()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    至此,我们已经完成了单个页面的数据爬取和存储,接下来只要设计循环,实现顺序爬取所有页面即可。

    关于文件读取的类型

    本文中,用到的文件读取类型主要有:

    类型含义使用示例
    'w'清空原文档,重新写入文档open(filename, 'w')
    'r'仅读取文档,不改变其内容open(filename, 'r')
    'a'在原文档之后追加内容open(filename, 'a')

    顺序爬取所有页面

    我们注意到,正文的每个页面底部,都有一个按钮下一页,其在网页中的结构为:

    我们在XPath路径的末尾添加 @href 用于获取属性 href 的值:

    //*[@id="container"]/div/div/div[2]/div[3]/a[3]/@href
    
    • 1

    这里有一个小细节需要注意!

    我们获取到的属性值在不同页面可能是不一样的,比如:

    • 第一章第一页中,“下一页”指向的链接为/255/255036/4147599_2.html
    • 第一章第二页中,“下一页”指向的链接为4147600.html

    观察不同页面的链接,可以看出前缀是一致的,区别仅在后缀上,比如第一章第一页和第一章第二页的链接分别为:

    http://www.365kk.cc/255/255036/4147599.html
    http://www.365kk.cc/255/255036/4147599_2.html
    
    • 1
    • 2

    因此,我们只需要获取 {% kbd 下一页 %} 的链接后缀,再与前缀拼接,即可获得完整的访问链接。

    编写一个函数 next_url() 实现上述功能:

    # 获取下一页链接的函数
    def next_url(next_url_element):
        nxturl = 'http://www.365kk.cc/255/255036/'
        # rfind('/') 获取最后一个'/'字符的索引
        index = next_url_element.rfind('/') + 1
        nxturl += next_url_element[index:]
        return nxturl
    
    
    # 测试一下
    url1 = '/255/255036/4147599_2.html'
    url2 = '4147600.html'
    
    print(next_url(url1))
    print(next_url(url2))
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    输出如下所示:

    在爬取某一页面的内容后,我们获取下一页的链接,并请求该链接指向的网页,重复这一过程直到全部爬取完毕为止,即可实现正文的爬取。

    在这一过程中,需要注意的问题有:

    • 某一章节的内容可能分布在多个页面中,每个页面的章节标题是一致的,这一标题只需存储一次;
    • 请求网页内容的频率不宜过高,频繁地使用同一IP地址请求网页,会触发站点的反爬虫机制,禁止你的IP继续访问网站;
    • 爬取一次全文耗时较长,为了便于测试,我们需要先尝试爬取少量内容,代码调试完成后再爬取全文;
    • 爬取的起点为第一章第一页,爬取的终点可以自行设置;

    按照上述思想,爬取前6个页面作为测试,完整的代码如下:

    import requests
    from lxml import etree
    import time
    import random
    
    
    # 获取下一页链接的函数
    def next_url(next_url_element):
        nxturl = 'http://www.365kk.cc/255/255036/'
        # rfind('/') 获取最后一个'/'字符的索引
        index = next_url_element.rfind('/') + 1
        nxturl += next_url_element[index:]
        return nxturl
    
    
    # 请求头,需要添加你的浏览器信息才可以运行
    headers= {
        'User-Agent': '...',
        'Cookie': '...',
        'Host': 'www.365kk.cc',
        'Connection': 'keep-alive'
    }
    
    # 小说主页
    main_url = "http://www.365kk.cc/255/255036/"
    
    # 使用get方法请求网页
    main_resp = requests.get(main_url, headers=headers)
    
    # 将网页内容按utf-8规范解码为文本形式
    main_text = main_resp.content.decode('utf-8')
    
    # 将文本内容创建为可解析元素
    main_html = etree.HTML(main_text)
    
    bookTitle = main_html.xpath('/html/body/div[4]/div[1]/div/div/div[2]/div[1]/h1/text()')[0]
    author = main_html.xpath('/html/body/div[4]/div[1]/div/div/div[2]/div[1]/div/p[1]/text()')[0]
    update = main_html.xpath('/html/body/div[4]/div[1]/div/div/div[2]/div[1]/div/p[5]/text()')[0]
    introduction = main_html.xpath('/html/body/div[4]/div[1]/div/div/div[2]/div[2]/text()')[0]
    
    # 调试期间仅爬取六个页面
    maxPages = 6
    cnt = 0
    
    # 记录上一章节的标题
    lastTitle = ''
    
    # 爬取起点
    url = 'http://www.365kk.cc/255/255036/4147599.html'
    
    # 爬取终点
    endurl = 'http://www.365kk.cc/255/255036/4148385.html'
    
    while url != endurl:
        cnt += 1  # 记录当前爬取的页面
        if cnt > maxPages:
            break  # 当爬取的页面数超过maxPages时停止
    
        resp = requests.get(url, headers)
        text = resp.content.decode('utf-8')
        html = etree.HTML(text)
        title = html.xpath('//*[@class="title"]/text()')[0]
        contents = html.xpath('//*[@id="content"]/text()')
    
        # 输出爬取进度信息
        print("cnt: {}, title = {}, url = {}".format(cnt, title, url))
    
        with open(bookTitle + '.txt', 'a', encoding='utf-8') as f:
            if title != lastTitle:  # 章节标题改变
                f.write(title)      # 写入新的章节标题
                lastTitle = title   # 更新章节标题
            for content in contents:
                f.write(content)
                f.write('\n\n')
            f.close()
    
        # 获取"下一页"按钮指向的链接
        next_url_element = html.xpath('//*[@class="section-opt m-bottom-opt"]/a[3]/@href')[0]
    
        # 传入函数next_url得到下一页链接
        url = next_url(next_url_element)
    
        sleepTime = random.randint(2, 5)  # 产生一个2~5之间的随机数
        time.sleep(sleepTime)             # 暂停2~5之间随机的秒数
    
    print("complete!")
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87

    运行结果如下:


    数据清洗

    观察我们得到的文本文档,可以发现如下问题:

    • 缺乏书籍信息,如之前获取的书名、作者、最后更新时间和简介;
    • 切换页面时,尤其是同一章节的不同页面之间空行过多

    • 每章节第一段缩进与其他段落不一致;

    • 不同章节之间缺乏显眼的分隔符

    为了解决这些问题,我们编写一个函数 clean_data() 来实现数据清洗,代码如下:

    def clean_data(filename, info):
        """
        :param filename: 原文档名
        :param info: [bookTitle, author, update, introduction]
        """
    
        print("\n==== 数据清洗开始 ====")
    
        # 新的文件名
        new_filename = 'new' + filename
    
        # 打开两个文本文档
        f_old = open(filename, 'r', encoding='utf-8')
        f_new = open(new_filename, 'w', encoding='utf-8')
    
        # 首先在新的文档中写入书籍信息
        f_new.write('==  《' + info[0] + '》\r\n')  # 标题
        f_new.write('==  ' + info[1] + '\r\n')     # 作者
        f_new.write('==  ' + info[2] + '\r\n')     # 最后更新时间
        f_new.write("=" * 10)
        f_new.write('\r\n')
        f_new.write('==  ' + info[3] + '\r\n')     # 简介
        f_new.write("=" * 10)
        f_new.write('\r\n')
    
        lines = f_old.readlines()  # 按行读取原文档中的内容
        empty_cnt = 0  # 用于记录连续的空行数
    
        # 遍历原文档中的每行
        for line in lines:
            if line == '\n':        # 如果当前是空行
                empty_cnt += 1      # 连续空行数+1
                if empty_cnt >= 2:  # 如果连续空行数不少于2
                    continue        # 直接读取下一行,当前空行不写入
            else:                   # 如果当前不是空行
                empty_cnt = 0       # 连续空行数清零
            if line.startswith("\u3000\u3000"):  # 如果有段首缩进
                line = line[2:]                  # 删除段首缩进
                f_new.write(line)                # 写入当前行
            elif line.startswith("第"):          # 如果当前行是章节标题
                f_new.write("\r\n")              # 写入换行
                f_new.write("-" * 20)            # 写入20个'-'
                f_new.write("\r\n")              # 写入换行
                f_new.write(line)                # 写入章节标题
            else:                                # 如果当前行是未缩进的普通段落
                f_new.write(line)                # 保持原样写入
    
        f_old.close()  # 关闭原文档
        f_new.close()  # 关闭新文档
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    清洗后的文档中,每段段首无缩进,段与段之间仅空一行,不同章节之间插入20个字符 - 用以区分,上述问题得以解决。


    完整的代码

    需先填入自己浏览器的 headers 信息才可以直接运行!

    import requests
    from lxml import etree
    import time
    import random
    
    
    # 获取下一页链接的函数
    def next_url(next_url_element):
        nxturl = 'http://www.365kk.cc/255/255036/'
        # rfind('/') 获取最后一个'/'字符的索引
        index = next_url_element.rfind('/') + 1
        nxturl += next_url_element[index:]
        return nxturl
    
    
    # 数据清洗函数
    def clean_data(filename, info):
        """
        :param filename: 原文档名
        :param info: [bookTitle, author, update, introduction]
        """
    
        print("\n==== 数据清洗开始 ====")
    
        # 新的文件名
        new_filename = 'new' + filename
    
        # 打开两个文本文档
        f_old = open(filename, 'r', encoding='utf-8')
        f_new = open(new_filename, 'w', encoding='utf-8')
    
        # 首先在新的文档中写入书籍信息
        f_new.write('==  《' + info[0] + '》\r\n')  # 标题
        f_new.write('==  ' + info[1] + '\r\n')     # 作者
        f_new.write('==  ' + info[2] + '\r\n')     # 最后更新时间
        f_new.write("=" * 10)
        f_new.write('\r\n')
        f_new.write('==  ' + info[3] + '\r\n')     # 简介
        f_new.write("=" * 10)
        f_new.write('\r\n')
    
        lines = f_old.readlines()  # 按行读取原文档中的内容
        empty_cnt = 0  # 用于记录连续的空行数
    
        # 遍历原文档中的每行
        for line in lines:
            if line == '\n':        # 如果当前是空行
                empty_cnt += 1      # 连续空行数+1
                if empty_cnt >= 2:  # 如果连续空行数不少于2
                    continue        # 直接读取下一行,当前空行不写入
            else:                   # 如果当前不是空行
                empty_cnt = 0       # 连续空行数清零
            if line.startswith("\u3000\u3000"):  # 如果有段首缩进
                line = line[2:]                  # 删除段首缩进
                f_new.write(line)                # 写入当前行
            elif line.startswith("第"):          # 如果当前行是章节标题
                f_new.write("\r\n")              # 写入换行
                f_new.write("-" * 20)            # 写入20个'-'
                f_new.write("\r\n")              # 写入换行
                f_new.write(line)                # 写入章节标题
            else:                                # 如果当前行是未缩进的普通段落
                f_new.write(line)                # 保持原样写入
    
        f_old.close()
        f_new.close()
    
    
    # 请求头,使用前需填入自己浏览器的信息
    headers= {
        'User-Agent': '...',
        'Cookie': '...',
        'Host': 'www.365kk.cc',
        'Connection': 'keep-alive'
    }
    
    
    # 小说主页
    main_url = "http://www.365kk.cc/255/255036/"
    
    # 使用get方法请求网页
    main_resp = requests.get(main_url, headers=headers)
    
    # 将网页内容按utf-8规范解码为文本形式
    main_text = main_resp.content.decode('utf-8')
    
    # 将文本内容创建为可解析元素
    main_html = etree.HTML(main_text)
    
    bookTitle = main_html.xpath('/html/body/div[4]/div[1]/div/div/div[2]/div[1]/h1/text()')[0]
    author = main_html.xpath('/html/body/div[4]/div[1]/div/div/div[2]/div[1]/div/p[1]/text()')[0]
    update = main_html.xpath('/html/body/div[4]/div[1]/div/div/div[2]/div[1]/div/p[5]/text()')[0]
    introduction = main_html.xpath('/html/body/div[4]/div[1]/div/div/div[2]/div[2]/text()')[0]
    
    # 调试期间仅爬取六个页面
    maxPages = 6
    cnt = 0
    
    # 记录上一章节的标题
    lastTitle = ''
    
    # 爬取起点
    url = 'http://www.365kk.cc/255/255036/4147599.html'
    
    # 爬取终点
    endurl = 'http://www.365kk.cc/255/255036/4148385.html'
    
    while url != endurl:
        cnt += 1  # 记录当前爬取的页面
        # if cnt > maxPages:
        #     break  # 当爬取的页面数超过maxPages时停止
        
        resp = requests.get(url, headers)
        text = resp.content.decode('utf-8')
        html = etree.HTML(text)
        title = html.xpath('//*[@class="title"]/text()')[0]
        contents = html.xpath('//*[@id="content"]/text()')
    
        # 输出爬取进度信息
        print("cnt: {}, title = {}, url = {}".format(cnt, title, url))
        print(contents)
    
        with open(bookTitle + '.txt', 'a', encoding='utf-8') as f_new:
            if title != lastTitle:  # 章节标题改变
                f_new.write(title)      # 写入新的章节标题
                lastTitle = title   # 更新章节标题
            for content in contents:
                f_new.write(content)
                f_new.write('\n\n')
            f_new.close( )
    
        # 获取"下一页"按钮指向的链接
        next_url_element = html.xpath('//*[@class="section-opt m-bottom-opt"]/a[3]/@href')[0]
    
        # 传入函数next_url得到下一页链接
        url = next_url(next_url_element)
    
        sleepTime = random.randint(2, 5)  # 产生一个2~5之间的随机数
        time.sleep(sleepTime)             # 暂停2~5之间随机的秒数
    
        
    clean_data(bookTitle + '.txt', [bookTitle, author, update, introduction])
    print("complete!")
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143

    爬取过程如图:


    封装整理

    上述的开发流程基本上是想到哪写到哪,所有代码都堆在一个文件里,可读性较差

    可以参考Scrapy框架的规则,将不同作用的变量和代码分配到不同的文件中,提高整个项目的可读性。

    参考的文件结构如图所示:

    其中:

    • output 用于存储爬虫输出的文本文档
    • clean.py 用于实现数据清洗
    • items.py 用于定义需要爬取的变量
    • main.py 是整个项目的主函数
    • settings.py 用于定义请求头、请求间隔、代理IP等
    • spider.py 用于实现爬虫的基本逻辑
  • 相关阅读:
    05-prometheus的联邦模式-分布式监控
    蠕虫病毒流量分析案例
    docker mysql 启动报错
    nn.AdaptiveAvgPool2d(output_size)详解
    Matlab 点云迭代加权最小二乘法(IRLS)
    python线程join方法
    JavaScript【实例、静态方法与属性、原型链、instanceof 运算符、Object 对象的相关方法、对象的继承、多重继承、严格模式】(十九)
    [C#] FFmpeg 音视频开发总结
    数组15—join() : 将所有元素连接成一个字符串
    CSS详细汇总
  • 原文地址:https://blog.csdn.net/pyx2466079565/article/details/130796487