引言
网络爬虫,作为一种自动化获取网络信息的工具,就在这个探索中扮演着至关重要的角色。通过模拟人类浏览行为,它们能够在互联网的广阔海洋中捕捉到我们所需要的信息片段。但有时,直接获取的信息并不总是以我们期待的形式呈现。例如,在某些情况下,为了保护内容,网站可能会对数据进行编码或加密。这就需要我们不仅要有抓取数据的能力,更要有解码这些数据的智慧。
我们将从最基础的库引入开始,逐步深入到如何发送网络请求、解析HTML文档、识别并转换乱码编码,最终实现清晰文本的提取。
一、数据来源分析
目标对象
以爬取番茄小说网站中的文本为例。这里的目标对象分为:网址、小说标题、章节标题、内容、作者。
其次是明确抓包内容,我们这次爬虫需要获取的还有标题、章节标题、内容以及作者的数据。
我们要知道他们在HTML中对应的标签位置。
1)分析数据位置
a)进入目标对象网址,打开开发者工具(F12键/右键选择检查)
b)在开发者工具中选择(网络/network)并刷新目标对象网页,重新录制网络活动观察网络请求状态
c)通过开发者工具左上角小箭头选取网页对应的元素,了解对应标签位置,如下图:
图一 开发者工具选取HTML网页对应的元素
主标题获取方式已知晓,作者也在旁边也可以采用同样的方法。在这里细说标题跟内容爬取,我们要知道小说标题、内容在哪?我们通过浏览器手段去看小说怎么进入?怎么看到的?
图二 小说章节目录处
一般来说我们在看小说都是在目录点击观看的,但我们可以发现,目录下的每一章小说标题网页结构是相同的,如下图所示。
图三 小说章节目录标签结构
我们是通过其中的超链接进入的每个章节内容页面中的,点进其中一个超链接,进入到内容区
图四 小说内容区域的网络活动状态
通过网络状态中我们找到对应的返回过来的小说内容(如果找不到可以复制小说中的其中一段内容在网络状态中搜索)
2)分析不同章节的变化规律
第一章url:https://fanqienovel.com/reader/6751837026556838404
第二章url:https://fanqienovel.com/reader/6751837026573615627
可以看出小说两个章节之间的区别是reader/后面的id不同,通过不同的id跳转到不同的章节目录中去,即获取小说所有的id即可访问所有的章节内容
3)分析不同id可以从哪里获取
关于id的变化,在图二中有所提到,在该小说页面的主界面的目录下,存在着该小说的全部章节的跳转路由,在图三中我们也可以看到每个章节对应一个a标签。
<a href="/reader/6751837026556838404" class="chapter-item-title" target="_blank">第一章 受尽白眼</a>
像这样的a标签有很多,所以我们只需要获取当前小说主页的目录下的这些a标签中的href链接即可。
二、代码实现
爬虫的四个基本步骤
1.发送请求:爬虫需要与目标网站建立通信。我们通常会使用requests
这样的库来模拟浏览器发送HTTP请求。请求可以是简单的GET请求,用来索取数据;也可以是POST请求,提交表单信息等。为了使请求看起来更像是由真实用户发起,而非自动化脚本,爬虫往往需要设置请求头(Headers),包括用户代理(User-Agent)、认证令牌(Tokens)、Cookies等,来绕过网站的安全措施如CAPTCHA、限速等。
2.获取数据:一旦请求被发出并由服务器响应,爬虫会接收到包含目标信息的数据。这些数据通常是HTML格式,但也可能是JSON、XML或其他类型的数据。此时的数据是原始的,可能包括大量的结构化和非结构化信息,需要进一步的处理才能变得有用。
3.解析数据:获得数据后,爬虫需要处理并提取有用的信息。解析数据通常涉及HTML解析器如parsel
,它能够解析HTML文档,提取出需要的元素、属性等。通过CSS选择器或XPath表达式,可以精确地定位页面中的特定部分,用于提取文本、链接、图片地址等。某些情况下,还需要对数据进行解码或格式转换,以确保提取出的信息是准确和可读的。
4.保存数据:在提取并清洗好数据之后,最后一步便是将其保存起来,以便未来的使用和分析。数据可以以不同的格式保存,如写入文本文件、CSV、JSON,或存储到数据库中。选择最佳的存储方式通常取决于数据的类型和预期用途。例如,对于结构化数据,数据库是一个很好的选择;而对于单一数据类型或文本数据,文本文件或CSV文件可能更加方便。存储过程中还可能涉及到数据的进一步处理,例如数据去重、转换编码、数据清洗等,以确保保存的数据是高质量和一致的。
第一次请求:获取章节ID,小说名,章节名
发送请求:请求小说目录页:https://fanqienovel.com/page/6751820766397860871
获取数据:获取网页源代码
解析数据:提取章节的ID/章节名
第二次请求:获取内容
发送请求:请求连接章节链接
第一次请求获取到的章节ID(前面提到的链接:/reader/6751837026556838404)
将章节id拼接在前面固定的url后
获取数据:获取网页的源代码
解析数据:提取章节内容
保存数据:保存爬取出的小说内容
具体代码实现
******请注意******
headers = {
'Cookie': ‘’, #需要自己在浏览器中提供
'User-Agent': '' #需要自己在浏览器中提供
}
获取方式:在开发者工具中的网络下找到对应的网络状态,在标头处找到Cookie和User-Agent将他们复制在对应的''中,如下图所示
图五 获取Cookie和User-Agent
# 1. 导入必需的库
import requests # 发起网络请求
import parsel # 解析HTML
# 2. 番茄小说中的乱码字典,用于解码网页中的特定编码字符
dict_data = {
'58670': '0',
'58413': '1',
'58678': '2',
'58371': '3',
'58353': '4',
'58480': '5',
'58359': '6',
'58449': '7',
'58540': '8',
'58692': '9',
'58712': 'a',
'58542': 'b',
'58575': 'c',
'58626': 'd',
'58691': 'e',
'58561': 'f',
'58362': 'g',
'58619': 'h',
'58430': 'i',
'58531': 'j',
'58588': 'k',
'58440': 'l',
'58681': 'm',
'58631': 'n',
'58376': 'o',
'58429': 'p',
'58555': 'q',
'58498': 'r',
'58518': 's',
'58453': 't',
'58397': 'u',
'58356': 'v',
'58435': 'w',
'58514': 'x',
'58482': 'y',
'58529': 'z',
'58515': 'A',
'58688': 'B',
'58709': 'C',
'58344': 'D',
'58656': 'E',
'58381': 'F',
'58576': 'G',
'58516': 'H',
'58463': 'I',
'58649': 'J',
'58571': 'K',
'58558': 'L',
'58433': 'M',
'58517': 'N',
'58387': 'O',
'58687': 'P',
'58537': 'Q',
'58541': 'R',
'58458': 'S',
'58390': 'T',
'58466': 'U',
'58386': 'V',
'58697': 'W',
'58519': 'X',
'58511': 'Y',
'58634': 'Z',
'58611': '的',
'58590': '一',
'58398': '是',
'58422': '了',
'58657': '我',
'58666': '不',
'58562': '人',
'58345': '在',
'58510': '他',
'58496': '有',
'58654': '这',
'58441': '个',
'58493': '上',
'58714': '们',
'58618': '来',
'58528': '到',
'58620': '时',
'58403': '大',
'58461': '地',
'58481': '为',
'58700': '子',
'58708': '中',
'58503': '你',
'58442': '说',
'58639': '生',
'58506': '国',
'58663': '年',
'58436': '着',
'58563': '就',
'58391': '那',
'58357': '和',
'58354': '要',
'58695': '她',
'58372': '出',
'58696': '也',
'58551': '得',
'58445': '里',
'58408': '后',
'58599': '自',
'58424': '以',
'58394': '会',
'58348': '家',
'58426': '可',
'58673': '下',
'58417': '而',
'58556': '过',
'58603': '天',
'58565': '去',
'58604': '能',
'58522': '对',
'58632': '小',
'58622': '多',
'58350': '然',
'58605': '于',
'58617': '心',
'58401': '学',
'58637': '么',
'58684': '之',
'58382': '都',
'58464': '好',
'58487': '看',
'58693': '起',
'58608': '发',
'58392': '当',
'58474': '没',
'58601': '成',
'58355': '只',
'58573': '如',
'58499': '事',
'58469': '把',
'58361': '还',
'58698': '用',
'58489': '第',
'58711': '样',
'58457': '道',
'58635': '想',
'58492': '作',
'58647': '种',
'58623': '开',
'58521': '美',
'58609': '总',
'58530': '从',
'58665': '无',
'58652': '情',
'58676': '己',
'58456': '面',
'58581': '最',
'58509': '女',
'58488': '但',
'58363': '现',
'58685': '前',
'58396': '些',
'58523': '所',
'58471': '同',
'58485': '日',
'58613': '手',
'58533': '又',
'58589': '行',
'58527': '意',
'58593': '动',
'58699': '方',
'58707': '期',
'58414': '它',
'58596': '头',
'58570': '经',
'58660': '长',
'58364': '儿',
'58526': '回',
'58501': '位',
'58638': '分',
'58404': '爱',
'58677': '老',
'58535': '因',
'58629': '很',
'58577': '给',
'58606': '名',
'58497': '法',
'58662': '间',
'58479': '斯',
'58532': '知',
'58380': '世',
'58385': '什',
'58405': '两',
'58644': '次',
'58578': '使',
'58505': '身',
'58564': '者',
'58412': '被',
'58686': '高',
'58624': '已',
'58667': '亲',
'58607': '其',
'58616': '进',
'58368': '此',
'58427': '话',
'58423': '常',
'58633': '与',
'58525': '活',
'58543': '正',
'58418': '感',
'58597': '见',
'58683': '明',
'58507': '问',
'58621': '力',
'58703': '理',
'58438': '尔',
'58536': '点',
'58384': '文',
'58484': '几',
'58539': '定',
'58554': '本',
'58421': '公',
'58347': '特',
'58569': '做',
'58710': '外',
'58574': '孩',
'58375': '相',
'58645': '西',
'58592': '果',
'58572': '走',
'58388': '将',
'58370': '月',
'58399': '十',
'58651': '实',
'58546': '向',
'58504': '声',
'58419': '车',
'58407': '全',
'58672': '信',
'58675': '重',
'58538': '三',
'58465': '机',
'58374': '工',
'58579': '物',
'58402': '气',
'58702': '每',
'58553': '并',
'58360': '别',
'58389': '真',
'58560': '打',
'58690': '太',
'58473': '新',
'58512': '比',
'58653': '才',
'58704': '便',
'58545': '夫',
'58641': '再',
'58475': '书',
'58583': '部',
'58472': '水',
'58478': '像',
'58664': '眼',
'58586': '等',
'58568': '体',
'58674': '却',
'58490': '加',
'58476': '电',
'58346': '主',
'58630': '界',
'58595': '门',
'58502': '利',
'58713': '海',
'58587': '受',
'58548': '听',
'58351': '表',
'58547': '德',
'58443': '少',
'58460': '克',
'58636': '代',
'58585': '员',
'58625': '许',
'58694': '稜',
'58428': '先',
'58640': '口',
'58628': '由',
'58612': '死',
'58446': '安',
'58468': '写',
'58410': '性',
'58508': '马',
'58594': '光',
'58483': '白',
'58544': '或',
'58495': '住',
'58450': '难',
'58643': '望',
'58486': '教',
'58406': '命',
'58447': '花',
'58669': '结',
'58415': '乐',
'58444': '色',
'58549': '更',
'58494': '拉',
'58409': '东',
'58658': '神',
'58557': '记',
'58602': '处',
'58559': '让',
'58610': '母',
'58513': '父',
'58500': '应',
'58378': '直',
'58680': '字',
'58352': '场',
'58383': '平',
'58454': '报',
'58671': '友',
'58668': '关',
'58452': '放',
'58627': '至',
'58400': '张',
'58455': '认',
'58416': '接',
'58552': '告',
'58614': '入',
'58582': '笑',
'58534': '内',
'58701': '英',
'58349': '军',
'58491': '候',
'58467': '民',
'58365': '岁',
'58598': '往',
'58425': '何',
'58462': '度',
'58420': '山',
'58661': '觉',
'58615': '路',
'58648': '带',
'58470': '万',
'58377': '男',
'58520': '边',
'58646': '风',
'58600': '解',
'58431': '叫',
'58715': '任',
'58524': '金',
'58439': '快',
'58566': '原',
'58477': '吃',
'58642': '妈',
'58437': '变',
'58411': '通',
'58451': '师',
'58395': '立',
'58369': '象',
'58706': '数',
'58705': '四',
'58379': '失',
'58567': '满',
'58373': '战',
'58448': '远',
'58659': '格',
'58434': '士',
'58679': '音',
'58432': '轻',
'58689': '目',
'58591': '条',
'58682': '呢'
}
# 3. 模拟浏览器的请求头,伪装成浏览器用户,避免被网站拦截
headers = {
'Cookie': '', #根据实际情况需要自己在浏览器中提供
'User-Agent': '' #根据实际情况需要自己在浏览器中提供
}
# 4. 发送请求,获取网页内容
url = 'https://fanqienovel.com/page/6751820766397860871'
response = requests.get(url=url, headers=headers)
html = response.text
# 5. 使用parsel提取小说名称、章节标题和链接
selector = parsel.Selector(html)
name = selector.css('.info-name h1::text').get() # 提取小说名称
title_list = selector.css('.chapter-item-title::text').getall() # 提取章节标题列表
href = selector.css('.chapter-item-title::attr(href)').getall() # 提取链接后缀列表
# 6. 调整章节链接列表,移除列表中的第一个元素,因为它不是目标章节的一部分
href.pop(0)
# print(href)
# 7. 循环提取每个章节的内容
for title, link in zip(title_list, href):
link_url = 'https://fanqienovel.com' + link # 构造完整的章节URL
link_data = requests.get(url=link_url, headers=headers).text # 请求章节URL并获取内容
link_selector = parsel.Selector(link_data)
content_list = link_selector.css('.muye-reader-content-16 p::text').getall() # 提取章节文本内容
# 解码章节文本,将乱码字符映射为实际字符
novel_content = ''
for paragraph in content_list:
# 解码段落并添加到novel_content
for index in paragraph:
try:
word = dict_data[str(ord(index))] # 尝试根据字符的Unicode码点在字典中查找映射
except KeyError:
word = index # 如果查找失败,使用原始字符
novel_content += word # 将解码后的字符追加到章节内容中
novel_content += '\n' # 每个段落后添加两个换行符
print(title) # 打印章节标题
print(link_url) # 打印章节url
# print(novel_content) # 打印解码后的章节内容
# 写入文件,并保存爬取内容
with open(name + '.txt', mode='a', encoding='utf-8') as f:
f.write(title)
f.write('\n')
f.write(novel_content)
f.write('\n')
代码及注释附上。