目录
前言
使用爬虫时请勿做违法行为,案例仅供学习,后果自负。
技术要求
- 会python(如果文中用到的技术发现不会,那确实得去学习了)
- 熟悉计算机网络中的http请求
- 会用requests库,当然urllib也是一样,文中用到的库为requests
- 可能会使用epub格式,库使用的是ebooklib(可选)
- 会多线程(可选,你要是不怕慢可以单线程)
- 会正则表达式(这样子获得信息更快捷)
- 会python的文件操作
pip install requests
pip install ebooklib
安装完库之后就可以开始学习了
注:本文略微有点长,在编辑的时候显示了10900字,大家可以先点赞收藏,等到有时间的时候再看与学习。
一、调研要爬取的网站
我们要根据实际情况考量,比如你要爬取的页面是有图片的你的根据网站特性来分析是base64编码之后的还是直接从cdn传输过来的,跟html的关系又是什么,假如是视频的话得记得打开流传输。
本次的目标是笔趣阁_最新笔趣阁小说阅读网站,小说免费阅读网,属于单纯文本,当然书也是有封面的,我们也可以获取封面,也就是文字与图片。
二、分析url
根据调研我们发现这次的爬虫会爬到文字与图片,然后我们继续分析url,通过猛猛的一通乱点我们发现当为书籍的时候url为 https://域名/book/book_id
而且每一章的信息都为前面书籍的url/chapter_id.html
这样下来我们的url就分析完毕了
三、审查内容
分析书籍
这一步尤其的重要,假如你作为新手训练的话,你就得避开有些有加密的内容,因为这样子你就得去把他加密的方式去解密了,但是放心,本次的目标并没有做加密,所有内容都是明文传播。可以放心作为练手的对象。
这个时候我们需要打开F12,使用开发者工具进行内容的选取并且与其他的书籍进行对照分析。
然后我们可以发现<h1></h1>这个标签里的内容是会变的,还有h1上面的img,也就是书籍的封面,也是会变的。
然后我们还发现,每一章都是被包在dd标签里面
然后我们就可以根据有多少个dd就能确定有多少个章节,这里具体怎么存相信大家都有想法了,首先我们先用正则表达式取出每一个dd然后取h1获得标题名,甚至还可以保存封面图片。
书籍获得信息代码
import re
import requests
if __name__ == '__main__':
book_id = 3311
url = f"https://www.qu70.cc/book/{book_id}/"
text = requests.get(url).text
title = re.findall(r'<h1>.*</h1>', text)[0][4:-5] # 通过正则匹配标题然后再把多余的<h1>和</h1>切除掉
author = re.findall(r'作者:\w*', text)[0][3:] # 这个也切一下前面的作者:
img = re.findall('"\\S+', re.findall(r'<img src=".*"', text)[0])[0][1:-1] # 同理,但是再正则一遍再切
chapters = re.findall(rf'<dd><a href ="/book/{book_id}/\d*\.html">.*</a></dd>', text) # 直接检测获得多少个dd
# print(chapters)
print(len(chapters)) # 然后我们可以通过dd的长度来从1到len(chapters)来进行获取
print(title)
print(author)
print(img)
所以我们就取出了关键信息,标题,作者名字,图片url,和章节长度,因为都是从1开始,所以我们可以不再对每个都切获得信息直接使用len来获得最大长度,在后续直接通过长度进行遍历获得
分析单章可获得的信息
因为标题的话我们在单个页面里面也可以获得,所以我们就换个思路,从每个章节页面获得对应的标题
通过我们对html页面的再分析,我们可以确定,又是h1获得了标题,然后这个div获得了内容,我们继续编写代码获得每一章的这些信息
对应章节代码
import re
import requests
def analyze(book_id):
url = f"https://www.qu70.cc/book/{book_id}/"
text = requests.get(url).text
# 通过正则匹配标题然后再把多余的<h1>和</h1>切除掉
title = re.findall(r'<h1>.*</h1>', text)[0][4:-5]
# 这个也切一下前面的作者:
author = re.findall(r'作者:\w*', text)[0][3:]
# 同理,但是再正则一遍再切
img = re.findall('"\\S+', re.findall(r'<img src=".*"', text)[0])[0][1:-1]
# 直接检测获得多少个dd
chapters_len = len(re.findall(rf'<dd><a href ="/book/{book_id}/\d*\.html">.*</a></dd>', text))
return {"title": title, "author": author, "img": img, "chapters": chapters_len}
def chapter_get_content(book_id, chapter_id):
url = f"https://www.qu70.cc/book/{book_id}/{chapter_id}.html"
text = requests.get(url).text
chapter_title = re.findall(r'>.*</h1>', text)[0][1:-5]
content = re.findall('<div id="chaptercontent".*请收藏本站', text)[0][59:-7]
return {"chapter_title": chapter_title, "content": content}
if __name__ == '__main__':
book_id = 3311
chapter_get_content(book_id, 1)
首先我们对之前分析书籍的那个页面的代码进行了封装,变成了一个方法(函数),然后再把我们目前要开发的有关单个章节的内容和标题的爬取也变成一个方法,所以到时候我们只需要在循环里面每个章节都调用一下就可以了
四、保存爬取的内容
目前我们已经完成了基本的爬取了,现在只剩下保存到本地了,我们可以直接保存为单个txt文件,但是这样子会太大。
所以我们也可以保存为多个txt文件,每个章节保存一个文件但是可以放入一些阅读器里面。
当然我们还有个最厉害的操作,就是直接制作为epub格式的文件,但是我们制作epub格式为了方便所以只做存的操作,如果大家想要追更一个还在更新的小说,可以自己拓展写一个先读取epub和获取到的信息对比,然后找到不存在的塞入即可
我们先从单个txt文件开始讲起
1.保存为单txt文件
这个就非常简单了,首先我们爬取的是html的信息,我们可以看到首行空两个的占位符\u3000,这个自然毫无关系,但是末尾的换行还是属于html的<br/>所以我们要替换<br/>为换行的\n,也非常的简单,python的字符串里是有replace方法可以调用的。
import re
import os
import traceback
import requests
def analyze(book_id):
url = f"https://www.qu70.cc/book/{book_id}/"
text = requests.get(url).text
# 通过正则匹配标题然后再把多余的<h1>和</h1>切除掉
title = re.findall(r'<h1>.*</h1>', text)[0][4:-5]
# 这个也切一下前面的作者:
author = re.findall(r'作者:\w*', text)[0][3:]
# 同理,但是再正则一遍再切
img = re.findall('"\\S+', re.findall(r'<img src=".*"', text)[0])[0][1:-1]
# 直接检测获得多少个dd
chapters_len = len(re.findall(rf'<dd><a href ="/book/{book_id}/\d*\.html">.*</a></dd>', text))
return {"title": title, "author": author, "img": img, "chapters": chapters_len}
def chapter_get_content(book_id, chapter_id):
url = f"https://www.qu70.cc/book/{book_id}/{chapter_id}.html"
text = requests.get(url).text
chapter_title = re.findall(r'>.*</h1>', text)[0][1:-5]
content = re.findall('<div id="chaptercontent".*请收藏本站', text)[0][59:-7]
return {"chapter_title": chapter_title, "content": content}
def save_as_single_file(book_id, book_name, chapter_len):
# 我们使用os创建一个目录,可以让我们的单个txt文件保存在这个目录下
os.makedirs("txt/single", exist_ok=True)
i = 1
# 这里解释一下为什么要使用while而不是for,因为for循环相当于是一个迭代器,无论报错与否他都会跳到下一个,没法重复本次循环。
# 如果是我理解错了希望可以在评论区纠正一下并且给出对应代码,我本职并不是写python的[doge]
while i <= chapter_len:
try:
content = chapter_get_content(book_id, i)
s = content['content'].replace('<br /><br />', "\n")
with open(f"txt/single/{book_name}.txt", "at", encoding="utf-8") as f:
f.write(content['chapter_title'] + "\n")
f.write(s + "\n") # 前一章的底部到第二章的标题空一行
i += 1
except:
traceback.print_exc()
if __name__ == '__main__':
book_id = 3311
book_info = analyze(book_id)
save_as_single_file(book_id, book_info['title'], book_info['chapters'])
2.保存为多个txt文件
这个需要注意的是某些作者会把我们文件名不能出现的东西放在标题里面,还有一些作者会因为一天有两三更会在标题上标注1/3,2/3,3/3如此的,这样会导致我们的系统进行文件分类,所以我们需要做的是,把这些可能会出现的违规词替换掉。
import re
import os
import traceback
import requests
def analyze(book_id):
url = f"https://www.qu70.cc/book/{book_id}/"
text = requests.get(url).text
# 通过正则匹配标题然后再把多余的<h1>和</h1>切除掉
title = re.findall(r'<h1>.*</h1>', text)[0][4:-5]
# 这个也切一下前面的作者:
author = re.findall(r'作者:\w*', text)[0][3:]
# 同理,但是再正则一遍再切
img = re.findall('"\\S+', re.findall(r'<img src=".*"', text)[0])[0][1:-1]
# 直接检测获得多少个dd
chapters_len = len(re.findall(rf'<dd><a href ="/book/{book_id}/\d*\.html">.*</a></dd>', text))
return {"title": title, "author": author, "img": img, "chapters": chapters_len}
def chapter_get_content(book_id, chapter_id):
url = f"https://www.qu70.cc/book/{book_id}/{chapter_id}.html"
text = requests.get(url).text
chapter_title = re.findall(r'>.*</h1>', text)[0][1:-5]
content = re.findall('<div id="chaptercontent".*请收藏本站', text)[0][59:-7]
return {"chapter_title": chapter_title, "content": content}
def save_as_single_file(book_id, book_name, chapter_len):
# 我们使用os创建一个目录,可以让我们的单个txt文件保存在这个目录下
os.makedirs("txt/single", exist_ok=True)
i = 1
# 这里解释一下为什么要使用while而不是for,因为for循环相当于是一个迭代器,无论报错与否他都会跳到下一个,没法重复本次循环。
# 如果是我理解错了希望可以在评论区纠正一下并且给出对应代码,我本职并不是写python的[doge]
while i <= chapter_len:
try:
content = chapter_get_content(book_id, i)
s = content['content'].replace('<br /><br />', "\n")
with open(f"txt/single/{book_name}.txt", "at", encoding="utf-8") as f:
f.write(content['chapter_title'] + "\n")
f.write(s + "\n") # 前一章的底部到第二章的标题空一行
i += 1
except:
traceback.print_exc()
def replace_special_character(title):
search = re.findall(r'[/:*?"<>|\\]', title)
if len(search) > 0:
title = title.replace("*", "星")
title = title.replace("/", "丿")
title = title.replace(":", ":")
title = title.replace("?", "?")
title = title.replace("<", "《")
title = title.replace(">", "》")
title = title.replace("\\", "、")
title = title.replace("|", "丨")
title = title.replace("\"", "'")
return title
def save_multiple_file(book_id, book_name, chapter_len):
os.makedirs(f"txt/multiple/{book_name}", exist_ok=True)
i = 1
while i <= chapter_len:
try:
content = chapter_get_content(book_id, i)
chapter_title = content['chapter_title']
s = replace_special_character(content['content'].replace('<br /><br />', "\n"))
with open(f"txt/multiple/{book_name}/{chapter_title}.txt", "wt", encoding="utf-8") as f:
f.write(chapter_title + "\n")
f.write(s)
i += 1
except:
traceback.print_exc()
if __name__ == '__main__':
book_id = 3311
book_info = analyze(book_id)
save_multiple_file(book_id, book_info['title'], book_info['chapters'])
整合了单个文件,然后也有多个文件的保存方案。
3.epub格式保存
要写epub的时候需要注意挺多的东西的,就是epub的话他的每一章都是类html一样的xhtml,文中的写法是确定正确的,大家肯定跟着进行发散思维。然后需要设置的元数据也非常的多比如,identifier,title,language,author,cover,目录之类的
import re
import os
import traceback
import requests
from ebooklib import epub
def analyze(book_id):
url = f"https://www.qu70.cc/book/{book_id}/"
text = requests.get(url).text
# 通过正则匹配标题然后再把多余的<h1>和</h1>切除掉
title = re.findall(r'<h1>.*</h1>', text)[0][4:-5]
# 这个也切一下前面的作者:
author = re.findall(r'作者:\w*', text)[0][3:]
# 同理,但是再正则一遍再切
img = re.findall('"\\S+', re.findall(r'<img src=".*"', text)[0])[0][1:-1]
# 直接检测获得多少个dd
chapters_len = len(re.findall(rf'<dd><a href ="/book/{book_id}/\d*\.html">.*</a></dd>', text))
return {"title": title, "author": author, "img": img, "chapters": chapters_len}
def chapter_get_content(book_id, chapter_id):
url = f"https://www.qu70.cc/book/{book_id}/{chapter_id}.html"
text = requests.get(url).text
chapter_title = re.findall(r'>.*</h1>', text)[0][1:-5]
content = re.findall('<div id="chaptercontent".*请收藏本站', text)[0][59:-7]
return {"chapter_title": chapter_title, "content": content}
def save_as_single_file(book_id, book_name, chapter_len):
# 我们使用os创建一个目录,可以让我们的单个txt文件保存在这个目录下
os.makedirs("txt/single", exist_ok=True)
i = 1
# 这里解释一下为什么要使用while而不是for,因为for循环相当于是一个迭代器,无论报错与否他都会跳到下一个,没法重复本次循环。
# 如果是我理解错了希望可以在评论区纠正一下并且给出对应代码,我本职并不是写python的[doge]
while i <= chapter_len:
try:
content = chapter_get_content(book_id, i)
s = content['content'].replace('<br /><br />', "\n")
with open(f"txt/single/{book_name}.txt", "at", encoding="utf-8") as f:
f.write(content['chapter_title'] + "\n")
f.write(s + "\n") # 前一章的底部到第二章的标题空一行
i += 1
except:
traceback.print_exc()
def replace_special_character(title):
search = re.findall(r'[/:*?"<>|\\]', title)
if len(search) > 0:
title = title.replace("*", "星")
title = title.replace("/", "丿")
title = title.replace(":", ":")
title = title.replace("?", "?")
title = title.replace("<", "《")
title = title.replace(">", "》")
title = title.replace("\\", "、")
title = title.replace("|", "丨")
title = title.replace("\"", "'")
return title
def save_multiple_file(book_id, book_name, chapter_len):
os.makedirs(f"txt/multiple/{book_name}", exist_ok=True)
i = 1
while i <= chapter_len:
try:
content = chapter_get_content(book_id, i)
chapter_title = content['chapter_title']
s = replace_special_character(content['content'].replace('<br /><br />', "\n"))
with open(f"txt/multiple/{book_name}/{chapter_title}.txt", "wt", encoding="utf-8") as f:
f.write(chapter_title + "\n")
f.write(s)
i += 1
except:
traceback.print_exc()
def epub_chapter(book, book_info):
spine = []
# 获得content列表
i = 1
while i <= book_info["chapters"]:
try:
content = chapter_get_content(book_id, i)
chapter_title = content['chapter_title']
s = content['content']
# 每一个章节需要是和html类似的格式才能保存,所以我们的<br>就可以不删了,假如你觉得隔太远的话,你可以适当的把<br/ ><br />变成一个
chapter = epub.EpubHtml(title=chapter_title, file_name=chapter_title + '.xhtml',
content="<h1>" + chapter_title + "</h1>" + s)
book.add_item(chapter)
book.toc.append(chapter)
spine.append(chapter)
i += 1
except:
traceback.print_exc()
return spine
def save_epub_file(book_info: dict):
os.makedirs("epub/", exist_ok=True)
# 创建一个EPUB电子书对象
book = epub.EpubBook()
# 获得封面
cover = requests.get(url=book_info["img"]).content
spine = epub_chapter(book, book_info)
# 设置电子书的元数据
book.set_identifier(book_info['title']) # 设置唯一标识
book.set_title(book_info['title']) # 设置标题
book.set_language('zh') # 设置语言
book.add_author(book_info['author']) # 添加作者 不强制
book.set_cover(file_name="cover.jpg", content=cover) # 设置封面 不强制
book.add_item(epub.EpubNcx()) # 这是目录 支持epub 2.0
book.add_item(epub.EpubNav()) # 也是目录 支持epub 3.0
book.spine = spine # 目录里的内容
epub.write_epub("epub/《" + book_info['title'] + "》.epub", book)
if __name__ == '__main__':
book_id = 3311
book_info = analyze(book_id)
# save_as_single_file(book_id, book_info['title'], book_info['chapters'])
# save_multiple_file(book_id, book_info['title'], book_info['chapters'])
save_epub_file(book_info)
五、自由发挥
本文已经过万字了,算上代码,相信到这里大家已经挺会写的了,要是对线程理解相对较深的话可以稍微改造改造就能进行十倍速的爬取,起十个线程,从1到10,分别对应1,11,21到2,12,22一直到10,20,30,这样子,分开来就不会出现线程不安全的问题了,报错了的话因为异常处理也会继续爬取,直到爬取到为止。
结束语
我在挺久之前也上传过github,虽然不是一模一样但也是大差不差,原理都是一个原理,只是代码不同,在这个链接https://github.com/XingzaiUnrivaled/Novel_Spider
希望大家都能找到好工作,身体健康,万事如意,如果对你有帮助的话不要忘记点赞收藏