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

使用 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)

运行代码前,需要向 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)


输出结果如下:

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


正文爬取与解析

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

单个页面数据的获取

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

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

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

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)

输出结果如下:

可以看出,我们成功获取了小说第一章第一页的标题和正文部分,接下来我们将它储存在一个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()

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

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

储存好的内容中,大段的文字堆积在一起,而原文确实有着段落的区分,因此我们在储存文件时,每储存一段,就写入两个换行符 \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()

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

关于文件读取的类型

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

类型含义使用示例
'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

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

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

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

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

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

因此,我们只需要获取 {% 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))


输出如下所示:

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

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

  • 某一章节的内容可能分布在多个页面中,每个页面的章节标题是一致的,这一标题只需存储一次;
  • 请求网页内容的频率不宜过高,频繁地使用同一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!")


运行结果如下:


数据清洗

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

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

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

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

为了解决这些问题,我们编写一个函数 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()  # 关闭新文档

清洗后的文档中,每段段首无缩进,段与段之间仅空一行,不同章节之间插入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
 **自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**

**深知大多数Python工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

**因此收集整理了一份《2024年Python开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**

![img](https://img-blog.csdnimg.cn/img_convert/77861dcc6bb3f554f0289bf3d858a6dd.png)

![img](https://img-blog.csdnimg.cn/img_convert/c3d20063753e7b16a7a0090cfdb3188f.png)

![img](https://img-blog.csdnimg.cn/img_convert/731f7af09770cf2472e4c70c9a60a072.png)

![img](https://img-blog.csdnimg.cn/img_convert/e76b77724fca20debe9aebb501d20b19.png)

![img](https://img-blog.csdnimg.cn/img_convert/6c361282296f86381401c05e862fe4e9.png)

![img](https://img-blog.csdnimg.cn/img_convert/9f49b566129f47b8a67243c1008edf79.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!**

**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**

**如果你觉得这些内容对你有帮助,可以扫码获取!!!(备注:Python)**

**

**因此收集整理了一份《2024年Python开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**

[外链图片转存中...(img-XLIgc0Fg-1713678582494)]

[外链图片转存中...(img-ymwGHND5-1713678582495)]

[外链图片转存中...(img-8SB2gKQw-1713678582496)]

[外链图片转存中...(img-ypah9Nxj-1713678582496)]

![img](https://img-blog.csdnimg.cn/img_convert/6c361282296f86381401c05e862fe4e9.png)

![img](https://img-blog.csdnimg.cn/img_convert/9f49b566129f47b8a67243c1008edf79.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!**

**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**

**如果你觉得这些内容对你有帮助,可以扫码获取!!!(备注:Python)**

![](https://img-blog.csdnimg.cn/img_convert/23631346459cf281cdf0bfb4b3944c87.jpeg)
  • 26
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值