前言
现在喜欢看小说的朋友越来越多了,但是每次都要到某个网站去看有很麻烦。特别是有时候回乡下老家,信号差数据网络用不了,有没有WIFI。所以,要是能提前把小说下载好就OK了。
准备
其实,没什么要准备的。爬虫经常用的那几个模块就OK了。今天只用到了三个模块,分别是requests、json和re。
开始正题
抓包
其实,爬虫的步骤都是非常固定的。第一步,还是抓包,随便选一本小说然后点击免费试读,小说的内容就呈现在我们面前了。点击目录,发现地址栏的地址并没有改变过,所以就是阿贾克斯请求了,直接在Fetch/XHR中找数据包。
果不其然,在下面这个数据包中就找到了所有章节的json数据
层层展开,发现每个索引下面有这些内容
接下来就是寻找这些数据和每一章节的关系,我观察了三章的地址栏,很容易就找出了规律,和我之前做的那个音乐爬虫如出一辙。
第二章
第三章
第四章
大家很容易就发现了,这个地址的前面的所有字符都是没有改变的,唯一改变了的就是最后一个数据。而这个数据,大家仔细观察,也很容易发现,它就是我们上面那个数据包中的章节索引下面的id对应的值。那么这样一来,每个章节的地址解决了。
接下来,我们就要来到每个章节的详情页,来想办法得到这本小说的文本内容。
我在“全部”的第一个数据包中,就找到了小说的文本内容
到这里,我们可以停下来,回头看看。到现在,我们已经得到了哪些有用信息。
1.每个章节的地址即URL
2.每个章节详情页的小说文本内容在哪
接下来,我们要做的就是根据这个URL获得小说文本内容就OK了
敲代码
首先,就是请求目录页的数据包,然后通过目录页的数据包得到每个章节的URL。在这里有一个需要注意的点,就是这个网站进行了一定程度的反爬,如果你不带上请求头就请求不到数据。
请求的URL和请求头就如上图所示,这样就可以请求到数据了。大家注意一下bookId,大家使用的话直接输入这个bookId就可以正常使用了,也就是我刚才展示的每个章节URL的倒数第二个数据,也就是倒数第二个位置的那串数字,就是小说的ID。还有一个点,就是请求头中其实有很多经过加密的字段,如果加上这些字段,是请求不到数据的,我没有将它们截图进来。 请求代码如下:
bookId = input('bookId:')
url = 'https://www.qidian.com/ajax/book/category?bookId={}&_csrfToken=mLzoQhBuJ9pPDdAm8VpBuvMFXCgQQAlhFrC9TuvU'.format(bookId)
headers = {
'Accept':'application/json, text/plain, */*',
'Accept-Encoding':'gzip, deflate, br',
'Accept-Language':'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
'Connection':'keep-alive',
'Cookie':'_csrfToken=mLzoQhBuJ9pPDdAm8VpBuvMFXCgQQAlhFrC9TuvU; newstatisticUUID=1692151465_166373733; Hm_lvt_f00f67093ce2f38f215010b699629083=1692151469; fu=1381548093; _yep_uuid=ad752dda-9748-ea50-e98f-865f3b8bb989; _gid=GA1.2.1689446406.1692151469; supportwebp=true; supportWebp=true; trkf=1; qdrs=0%7C3%7C0%7C0%7C1; navWelfareTime=1692152350314; showSectionCommentGuide=1; qdgd=1; rcr=1036370336; bc=1036370336; e1=%7B%22pid%22%3A%22qd_P_mycenter%22%2C%22eid%22%3A%22qd_H_mall_bottomaddownload%22%2C%22l7%22%3A%22hddl%22%7D; e2=%7B%22l6%22%3A%22%22%2C%22pid%22%3A%22qd_p_qidian%22%2C%22eid%22%3A%22qd_A16%22%2C%22l1%22%3A3%7D; lrbc=1036370336%7C749524263%7C0; _gat_gtag_UA_199934072_2=1; traffic_utm_referer=https%3A%2F%2Flink.csdn.net%2F; Hm_lpvt_f00f67093ce2f38f215010b699629083=1692155018; _ga_FZMMH98S83=GS1.1.1692151469.1.1.1692155018.0.0.0; _ga_PFYW0QLV3P=GS1.1.1692151469.1.1.1692155018.0.0.0; _ga=GA1.2.2023067070.1692151469',
'Host':'www.qidian.com',
'Referer':'https://www.qidian.com/chapter/1023826840/573073457/',
'sec-ch-ua':'"Microsoft Edge";v="113", "Chromium";v="113", "Not-A.Brand";v="24"',
'sec-ch-ua-mobile':'?0',
'sec-ch-ua-platform':'"Windows"',
'Sec-Fetch-Dest':'empty',
'Sec-Fetch-Mode':'cors',
'Sec-Fetch-Site':'same-origin',
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.42',
'X-D':'0'
}
response = requests.get(url=url, headers=headers)
还有一个点,直接输出text会出现乱码,我们手动设置一下编码格式为utf-8
response.encoding = 'utf-8'
然后,就是用正则表达式来提取文本中的id值了,当然还有每个章节的名称。
ex = '{"uuid":.*?,"cN":"(.*?)","uT":.*?,"cnt":.*?,"cU":.*?,"id":(.*?),"sS":.*?}'
data = re.findall(ex, response.text, re.S)
至此,咱们就得到了每个章节的URL和每个章节的名称了。
数据提取
接下来,咱们就要对每个章节的数据进行提取了。当然首先是要构成每个章节的URL,然后再对每个章节的URL进行请求。然后继续用正则表达式对小说文本进行提取,代码如下:
chapterUrlList = []
titleList = []
bookBodyList = []
print("正在获取书籍内容,请耐心等待哦!")
for i in data:
# 获取每一章的章名
title = i[0]
titleList.append(title)
# 获取每一章的链接地址
chapterUrl = 'https://www.qidian.com/chapter/{}/'.format(bookId) + i[-1] + '/'
chapterUrlList.append(chapterUrl)
# 用正则表达式获取每一章的内容
book = requests.get(url = chapterUrl)
ex1 = '<main .*?><p>(.*?)</p></main>'
bookBody = re.findall(ex1, book.text, re.S)
# 用replace替换掉小说文本中的一些特殊符号
body = bookBody[0].replace('\u3000\u3000', '\n')
body = body.replace('<p>', '')
body = body.replace('</p>', '')
# 将文本保存到列表中
bookBodyList.append(body)
# 获取小说名字和小说作者等信息
book_Info = requests.get(url = chapterUrl)
ex2 = '<script id="vite-plugin-ssr_pageContext" type="application/json">(.*?)</script>'
book_Info = re.findall(ex2, book_Info.text, re.S)[0]
json_data = json.loads(book_Info)
print(type(json_data))
bookName = json_data['pageContext']['pageProps']['pageData']['bookInfo']['bookName']
authorName = json_data['pageContext']['pageProps']['pageData']['bookInfo']['authorName']
print("正在保存,马上就好哦!")
最后就是保存请求来下的小说文本了
with open('书名:'+bookName + '作者' + authorName + '.txt', 'w') as f:
for j in range(0, len(titleList)):
f.write(titleList[j])
f.write(':')
f.write(chapterUrlList[j])
f.write(bookBodyList[j])
print("保存成功!")
到这里咱们就大功告成了!
注意事项
1.就是网站进行了一定程度的反爬,但是请求头中又有一些进行了加密的参数,如果全部加上也请求不到数据;如果一个不加也请求不到数据。
2.其实最麻烦的一个点还是数据提取,我们有的小说文本内容中,有很多html的语法符号,我们有必要把它们给清理掉。
3.正则表达式其实不太好配对,也可能是我学艺不精。因为我们从响应中直接将html标准的代码复制过来,它其中包含有换行符之类的符号是显现不出来的。在我们配对的时候就无法配对这些限制字符了。
完整代码我会上到资源里面的,有兴趣的可以下载哦。
仅供学习使用,请不要用于违法犯罪。