python爬虫实战(1)——网站小说

整本小说的爬取保存

  • 目标
  • 大致思路
  • 页面的爬取解析—XPath
    • 请求网页内容
    • 解析网页内容
    • 正文爬取与解析
      • 单个页面数据获取
      • 爬取所有页面
    • 数据清洗

经过学习基础,我们学以致用一下子,爬取小说,注意这个小说本身是免费的哦,以后再进阶。
本次为实战记录,笔者很多碎碎念可忽略

目标

基于requests库和lxml中的xpath编写的爬虫,目标小说网站域名http://www.365kk.cc/

这是网上找的,小说网址很多,而且没有很多反扒机制,新手友好!

大致思路

其实也可以不写,梳理一下吧,虽然是空话

主要分三步(如同大象进冰箱hh)

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

我们把要做的具体化,首先选取一本自己喜欢的小说,笔者以此为例,是的就是这么重口。
《我是一具尸体》

在这里插入图片描述

页面的爬取解析—XPath

要干嘛明确一下:

  • 获取目标书籍的基本信息,包括书籍的书名、作者、简介——这些信息应该都在同一个页面中获取,即上面展示的页面;
  • 获取目标书籍每一章节的标题和内容——不同章节在不同的页面,不同页面之间可以通过下一页按序跳转;
  • 正文部分的存储格式应便于阅读,不能把所有文字都堆积在一起,也不能包括除了正文之外的其他无关内容;

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

笔者是用Google Chrome,在首页点击右键 —> 检查:

在这里插入图片描述
可以看出,浏览器下方弹出了一个窗口,这里显示的就是该页面的源代码,我们选中的内容位于一个<h1>标签中。点击右键 -> 复制 -> 复制 XPath,即可得到书名的XPath路径,也就是书名在网页中的位置。

书名:/html/body/div[4]/div[1]/div/div/div[2]/div[1]/h1

从书籍的首页中,同理我们可以获取的信息主要包括:

  • 书名 作者 最后更新时间
作者:/html/body/div[4]/div[1]/div/div/div[2]/div[1]/div/p[1]
最后更新时间:/html/body/div[4]/div[1]/div/div/div[2]/div[1]/div/p[5]
简介:/html/body/div[4]/div[1]/div/div/div[2]/div[2]

请求网页内容

使用基础的python爬虫网页请求方法: requests 库直接请求。
在请求网页时,我们需要将我们的爬虫伪装成浏览器,具体通过添加请求头 headers 实现。
请求头以字典的形式创建,可以包括很多内容,这里只设置四个字段:User-Agent, Cookie, Host 和 Conection。

在刚才打开的页面中,点击 网络(英文版是Network),刷新页面,找到其中的第一个文件 1053/ ,打开 标头 -> 请求标头 ,即可得到想要的字段数据。
在这里插入图片描述

import requests

# 请求头,添加你的浏览器信息后才可以正常运行
headers= {
    'User-Agent': '...',
    'Cookie': '...',
    'Host': 'www.365kk.cc',
    'Connection': 'keep-alive'
}
# 小说主页
main_url = "http://www.365kk.cc/1/1053/"
# 使用get方法请求网页
main_resp = requests.get(main_url, headers=headers)
# 将网页内容按utf-8规范解码为文本形式
main_text = main_resp.content.decode('utf-8')
print(main_text)

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

解析网页内容

我们使用 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/1/1053/"
# 使用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]来表示列表中的首个元素
# /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]
# 输出结果以验证
print(bookTitle)
print(author)
print(update)
print(introduction)

输出结果如下:
在这里插入图片描述

至此,使用基本的网页请求get方法获取目标页面中的特定内容结束,接下来就是正文解析了,

go go go

正文爬取与解析

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

单个页面数据获取

打开第一章,获取章节标题和正文的XPath路径如下:
在这里插入图片描述

书名://*[@id="container"]/div/div/div[2]/h1
内容://*[@id="content"]

按照与上文一致的方法请求并解析网页内容,代码如下:
注意到在用这个xpath路径的时候我们是只要里面的文字部分所以要多加一个/text()

import requests
from lxml import etree

# 请求头
headers= {
    'User-Agent': '...',
    'Cookie': '...',
    'Host': 'www.365kk.cc',
    'Connection': 'keep-alive'
}

# 当前页面链接
url = 'http://www.365kk.cc/1/1053/10094192.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文本文档中,关于文件读取类型主要有(并不全,只是本代码用到的)

  • 'w': 清空原文档,重新写入文档 open(filename, ‘w’)
  • 'r': 仅读取文档,不改变其内容 open(filename, ‘r’)
  • 'a': 在原文档之后追加内容 open(filename, ‘a’)

文档命名为之前获取的书名 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/1/1053/"

# 使用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/1/1053/10094192.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)
        # 在储存文件时,每储存一段,就写入两个换行符 `\n`,避免大段文字堆积使格式更便于阅读
        f.write('\n\n')
    f.close()

运行结束后,可以看到在代码文件的同路径中,已经生成了一个文本文档。至此,我们已经完成了单个页面的数据爬取和存储,接下来只要设计循环,实现顺序爬取所有页面即可。
在这里插入图片描述

爬取所有页面

我们注意到,正文的每个页面底部,都有一个按钮下一页,其在网页中的结构为:
在这里插入图片描述
在XPath路径的末尾添加 @href 用于获取属性 href 的值:

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

注意到如果一章结束,下一页和下一章的链接就会有些差别

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

http://www.365kk.cc/1/1053/10094192.html
http://www.365kk.cc/1/1053/10094192_2.html

因此,我们只需要获取下一页的链接后缀,再与前缀拼接,即可获得完整的访问链接。代码如下:

# 获取下一页链接的函数
def next_url(next_url_element):
    nxturl = 'http://www.365kk.cc/1/1053/'
    # rfind('/') 获取最后一个'/'字符的索引
    index = next_url_element.rfind('/') + 1
    nxturl += next_url_element[index:]
    return nxturl

url1 = '/1/1053/10094192_2.html'
url2 = '10094193.html'

print(next_url(url1))
print(next_url(url2))

在爬取某一页面的内容后,我们获取下一页的链接,并请求该链接指向的网页,重复这一过程直到全部爬取完毕为止,即可实现正文的爬取。
在这里插入图片描述
在这一过程中,需要注意的问题有:

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

import requests
from lxml import etree
import time
import random


# 获取下一页链接的函数
def next_url(next_url_element):
    nxturl = 'http://www.365kk.cc/1/1053/'
    # 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/1/1053/"

# 使用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]

# 调试期间仅爬取4个页面
maxPages = 4
cnt = 0
# 记录上一章节的标题
lastTitle = ''

# 爬取起点
url = 'http://www.365kk.cc/1/1053/10094192.html'
# 爬取终点
endurl = 'http://www.365kk.cc/1/1053/10094194.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() 来实现数据清洗,清洗后的文档中,每段段首无缩进,段与段之间仅空一行,不同章节之间插入20个字符 - 用以区分,问题得以解决。
完整代码,需要自取哦
小说爬虫

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/70756.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ELK的搭建和使用

ELK的搭建和使用 1、什么是ELK 日志收集平台有多种组合方式&#xff1a; ELK Stack 方式&#xff1a;Elasticsearch Logstash Filebeat Kibana&#xff0c;业界最常见的架构。 Elasticsearch Logstash Kafka Kibana&#xff0c;用上了消息中间件&#xff0c;但里面也有…

Delphi7通过VB6之COM对象调用FreeBASIC写的DLL功能

VB6写ActiveX COM组件比较方便&#xff0c;不仅PowerBASIC与VB6兼容性好&#xff0c;Delphi7与VB6兼容性也不错&#xff0c;但二者与FreeBASIC兼容性在字符串处理上差距比较大&#xff0c;FreeBASIC是C化的语言&#xff0c;可直接使用C指令。下面还是以实现MKI/CVI, MKL/CVL, M…

iOS开发-实现二维码扫一扫Scan及识别图片中二维码功能

iOS开发-实现二维码扫一扫Scan及识别图片中二维码功能 在iOS开发中&#xff0c;会遇到扫一扫功能&#xff0c;扫一扫是使用摄像头扫码二维码或者条形码&#xff0c;获取对应二维码或条形码内容字符串。通过获得的字符串进行跳转或者打开某个页面开启下一步的业务逻辑。 https…

布局性能优化:安卓开发者不可错过的性能优化技巧

作者&#xff1a;麦客奥德彪 当我们开发Android应用时&#xff0c;布局性能优化是一个必不可少的过程。一个高效的布局能够提高用户体验&#xff0c;使应用更加流畅、响应更加迅速&#xff0c;而低效的布局则会导致应用的运行变得缓慢&#xff0c;甚至出现卡顿、崩溃等问题&…

npm ERR! code ERESOLVEnpm ERR! ERESOLVE unable to resolve dependency tree

拉取项目到本地 执行 npm install 报错 遇到这个问题首先确认的就是版本是不是太高了&#xff0c;降一下版本。或者通过yarn命令替代npm install命令安装&#xff0c;同理&#xff0c;启动也可以采用yarn dev 启动代替npm run dev 下面教大家用一个NVM工具&#xff0c;这个工…

SSL握手协议相关概念

下图为握手协议的流程图&#xff0c;具体的解释参考博客&#xff1a; 【下】安全HTTPS-全面详解对称加密&#xff0c;非对称加密&#xff0c;数字签名&#xff0c;数字证书和HTTPS_tenfyguo的博客-CSDN博客 下面梳理一下SSL协议中的一些细节。首先是相关名词&#xff1a;证书、…

深度学习基础知识笔记

深度学习要解决的问题 1 深度学习要解决的问题2 应用领域3 计算机视觉任务4 视觉任务中遇到的问题5 得分函数6 损失函数7 前向传播整体流程8 返向传播计算方法1 梯度下降 9 神经网络整体架构11 神经元个数对结果的影响12 正则化和激活函数1 正则化2 激活函数 13 神经网络过拟合…

前端开发常见效果

目录 css实现图像填充文字 css实现手风琴效果 css实现网站变灰色 elementUi的导航栏效果 css实现滚动吸附效果 鼠标经过&#xff0c;元素内部放大 css实现图像填充文字 效果图&#xff1a; 代码&#xff1a; <!DOCTYPE html> <html><head><meta c…

5.2 互联网通信安全

数据参考&#xff1a;CISP官方 目录 一、什么是互联网通信安全二、为什么要关注互联网通信安全三、电子邮件应用安全四、即时通讯应用安全 一、什么是互联网通信安全 1、互联网通信应用的概念 通信的进化史 互联网通信技术&#xff08;OSI七层模型&#xff09; 互联网应…

Effective Java笔记(29)优先考虑泛型

一般来说 &#xff0c;将集合声 明参数化&#xff0c;以及使用 JDK 所提供的泛型方法&#xff0c;这些都不太困难 。编写自己的泛型会比较困难一些&#xff0c;但是值得花些时间去学习如何编写 。 以简单的&#xff08;玩具&#xff09;堆校实现为例 &#xff1a; // Object -…

创新引领城市进化:人工智能和大数据塑造智慧城市新面貌

人工智能和大数据等前沿技术正以惊人的速度融入智慧城市的方方面面&#xff0c;为城市的发展注入了强大的智慧和活力。这些技术的应用不仅令城市管理更高效、居民生活更便捷&#xff0c;还为可持续发展和创新奠定了坚实的基础。 在智慧城市中&#xff0c;人工智能技术正成为城市…

分享一组天气组件

先看效果&#xff1a; CSS部分代码&#xff08;查看更多&#xff09;&#xff1a; <style>:root {--bg-color: #E9F5FA;--day-text-color: #4DB0D3;/* 多云 */--cloudy-background: #4DB0D3;--cloudy-temperature: #E6DF95;--cloudy-content: #D3EBF4;/* 晴 */--sunny-b…

TypeScript 中【class类】与 【 接口 Interfaces】的联合搭配使用解读

导读&#xff1a; 前面章节&#xff0c;我们讲到过 接口&#xff08;Interface&#xff09;可以用于对「对象的形状&#xff08;Shape&#xff09;」进行描述。 本章节主要介绍接口的另一个用途&#xff0c;对类的一部分行为进行抽象。 类配合实现接口 实现&#xff08;impleme…

中科亿海微RAM使用

引言 FPGA&#xff08;Field Programmable Gate Array&#xff0c;现场可编程门阵列&#xff09;是一种可编程逻辑设备&#xff0c;能够根据特定应用的需求进行配置和重新编程。在FPGA中&#xff0c;RAM&#xff08;Random Access Memory&#xff0c;随机存取存储器&#xff09…

Maven在IDEA2021版本中全局配置(一次配置处处生效)

前言 我们在开发中&#xff0c;Maven是必不可少的&#xff0c;但是每次都需要设置一遍Maven的仓库和settings.xml。真的是心累&#xff0c;今天教大家全局配置一下。再也不要每次项目都配了&#xff0c;Maven还经常出问题。 解决方案 友情提示&#xff1a;小编的IDEA版本为2…

【Fegin技术专题】「原生态」打开Fegin之RPC技术的开端,你会使用原生态的Fegin吗?(中)

你可以使用 Jersey 和 CXF 这些来写一个 Rest 或 SOAP 服务的java客服端。 你也可以直接使用 Apache HttpClient 来实现。但是 Feign 的目的是尽量的减少资源和代码来实现和 HTTP API 的连接。 *通过自定义的编码解码器以及错误处理&#xff0c;你可以编写任何基于文本的 HTT…

改进DevSecOps框架的 5 大关键技术

Markets and Markets的一项研究显示&#xff0c;全球DevOps的市场规模从2017年的29亿美元增加到2023年的103.1亿美元&#xff0c;预测期的年复合增长率(CAGR)为24.7%。人们对DevOps越来越感兴趣&#xff0c;因为DevOps不仅能够压缩软件的交付周期&#xff0c;还能提高交付的速度…

c++QT文件操作

1 介绍 QT的文件操作来源于其抽象基类QIODevice&#xff0c;中用于处理输入输出设备。提供了统一的接口来处理不同类型的数据源&#xff0c;如文件、套接字、缓冲区等。QIODevice 主要用于读取和写入数据&#xff0c;无论数据来自何种源头&#xff0c;都可以通过 QIODevice 统一…

HDFS中snapshot快照机制

HDFS中snapshot快照机制 介绍作用功能实现相关命令和操作相关命令 介绍 snapshot是数据存储的某一时刻的状态记录&#xff0c;备份&#xff08;backup&#xff09;则是数据存储的某一个时刻的副本HDFS snapshot快照是整个文件系统或某个目录在某个时刻的镜像&#xff0c;该镜像…

安路FPGA的赋值报错——移位处理,加括号

authordaisy.skye的博客_CSDN博客-嵌入式,Qt,Linux领域博主 在使用移位符号用来当作除以号使用时&#xff0c;发现如下问题 其中 cnt_8K 为偶数和奇数时输出的数据不一样 reg [10:0] cnt_8K; reg [10:0] ram1_addra; always(posedge clk_16M) begin if(ram_out_flag )begin if(…
最新文章