写在前面的废话

  1. 没错,这个爬虫的确只是我想统计下自己读小说的速度和自己已经读了多少小说写的,可以爬一些小说的数据,不能用来爬小说本身。不过稍加改进可以实现更多的功能,我会在之后的文章实现其他的功能。
  2. 话说我都好久没有发过文章了啊,所以就来水一篇博客了(x)

正文

一、使用工具和目标网站的分析

使用的工具,恩,如题,我使用的是 python3.7,其他版本问题应该也不大。
我用到的包有三个:

  • requests 用于发出http请求来获取数据
  • re 用来匹配正则表达式的(我没有使用 HTML 解析器)
  • time

然后是网站的分析了。
这次我拿 SF轻小说 http://book.sfacg.com/ 这个网站开刀。

对网站分析就不难整理接下来的思路了。网站自带收藏夹功能,而访问别人的收藏夹并不需要登录,因而在之后爬取数据的时候不需要模拟登录,可以比较方便地爬取数据。收藏夹内有各本小说的标题,作者,链接等数据,在小说页就可以看到字数以及更多我们想要的数据了。因此我们可以在阅读时把小说添加进收藏夹内,之后统计整个收藏夹就可以得到我们想要的数据了。

二、开工!

随便打开一个火袋页:
火袋(收藏夹)的页面
就可以看到里面的基本信息了。
比如这个火袋的 URL 是: http://p.sfacg.com/p/1892478/1/
不难得出,前面的 http://p.sfacg.com/p/ 就是每个火袋共有的前缀了。 1892478 是这个火袋的id, 最后的1/是页码,第一页默认不会有这个页码,这里是我加的。默认一页现实10本书,可以根据上面显示的总共的小说数计算出总页数。虽然最下面可以选择每页数量,但是有最大限制,当总数过多时就起不到作用了。所以这里就按照默认的数量来。

开 f12,分析下html:
找到总共小说的数量
先找到小说的总数。
点开F12分析html
然后可以看到,一个 <ul class="content_comment cover" eid="xxxx">就是一本小说了。点开标签还可以看到这本小说的封面标题作者等信息,不过这些我们都可以等进入详情页再去爬取。
SF轻小说的每本小说详情页的格式为: http://book.sfacg.com/Novel/xxx/ 这后面的xxx就是前面的 eid 了。
我们先试试把这个火袋里的所有小说爬出来:

三、开爬!

代码写出来大概是这个样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import requests
import re
import time

favlist = 'http://p.sfacg.com/p/1892478/'
headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36'}

req = requests.get(favlist, headers=headers)

pat_getcount = re.compile(r'<a href="javascript:void\(0\)">全部小说\(([0-9]*)\)</a>')
pat_getnovel = re.compile(r'<ul class="content_comment cover" eid="([0-9]*)">')

content = req.content.decode('utf-8')

pages = int(pat_getcount.findall(content)[0])
pages = pages // 10 + 2

for page in range(1,pages):
req = requests.get(favlist+str(page)+'/', headers=headers)
content = req.content.decode('utf-8')
eid_list = pat_getnovel.findall(content)
print(eid_list)

运行一下看看:
第一次运行结果
可以看到,这个火袋里所有小说的id都被爬下来了,之后只要拼接下字符串就可以得到每本小说的url了。
然后简单讲解下:
首先是三个包的导入,收藏夹地址和header的定义,就不用多讲了。
然后是正则表达式的定义。从html里可以找到相应的规律。因为小说的数量和eid都是数字,所以用 ([0-9]*) 来代替。写的时候注意下给原有的括号加上转义就行了。
总页数的计算可以用 总页数 = 小说总数 // 每页的小说数 + 1 来计算。代码中是 +2 而不是 +1 是为了后面的 range(1, pages) 方便一些所以这样的。
然后就是后面的遍历了。根据刚才算出来的页数遍历每一页,然后从每页提取相应的id,后面用id去访问详情页就可以了。
这里有一点,在开始获取总页数之后,第一页是不用再访问一遍的,不过为了方便些,我还是把第一页放到底下的for里了。
最后储存小说id的list也不会去打印了,之后会把所有的id放到一个list里然后去遍历的。

三、继续分析

接着刚才,随便打开一本小说的详情页看看:
小说详情页
可以看到小说的详情了,至少我想要的字数信息就在这里啦!
url的格式也很清楚了,最后的那串数字就是eid了:

1
2
3
base_url = 'http://book.sfacg.com/Novel/'
for eid in eid_list:
req = requests.get(base_url+eid+'/', headers=headers)

按f12看看源码:

详情页html
字数很好找,不过如果直接匹配 字数:([0-9]*) 的话会匹配到多个,加上前面的 span 标签就可以了。
对于标题,同理,加上前面的 h1 标签就行了。涉及到多行的时候使用 \s 匹配空白字符。

1
2
pat_gettitle = re.compile(r'<h1 class="title">\s*<span class="text">(.*?)</span>')
pat_getchara = re.compile(r'<span class="text">字数:([0-9]*)字')

最后别忘了用一个变量储存下总字数。最后的代码差不多是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import requests
import re
import time

favlist = 'http://p.sfacg.com/p/1892478/'
headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36'}

req = requests.get(favlist, headers=headers)

pat_getcount = re.compile(r'<a href="javascript:void\(0\)">全部小说\(([0-9]*)\)</a>')
pat_getnovel = re.compile(r'<ul class="content_comment cover" eid="([0-9]*)">')

content = req.content.decode('utf-8')

pages = int(pat_getcount.findall(content)[0])
pages = pages // 10 + 2
eid_list=[]

for page in range(1,pages):
req = requests.get(favlist+str(page)+'/', headers=headers)
content = req.content.decode('utf-8')
eid_list += pat_getnovel.findall(content)

pat_gettitle = re.compile(r'<h1 class="title">\s*<span class="text">(.*?)</span>')
pat_getchara = re.compile(r'<span class="text">字数:([0-9]*)字')

base_url = 'http://book.sfacg.com/Novel/'
all_character = 0
for eid in eid_list:
req = requests.get(base_url+eid+'/', headers=headers)
content = req.content.decode('utf-8')
title = pat_gettitle.findall(content)[0]
chara = pat_getchara.findall(content)[0]
print(title, chara)
all_character += int(chara)

print('All count of character: ', all_character)

运行下看看:
成功拿到想要的字数
成功!

总结

这次算是成功搞定了这个项目,对这个网站的目录页和小说详情页都有访问。虽然这次我只是想算下字数,但之后只要稍加改动便可以实现更多更复杂的效果,整体还是不错的,而且以后写其他代码时的参考性也很高。
这次的代码涉及到了requests库的使用,正则表达式还有html的审计等相关知识,以后想复习的时候可以回来参考下。

什么,你说我time库没有用?time库相关主要是用来计算时间以及通过sleep防止请求过快被封的情况的。你要问什么时候在哪用哪个相关的函数?这个我就先不讲了,自己研究吧2333(x)