我爬自己or我自己爬——记录在B站专栏上的一次Python爬虫应用
一、问题描述
获取我自己写的n篇UnderMine专栏中的166件圣物数据,将它们的效果、简评、评分按编号顺序录入已有的excel表格中,作为UM圣物查询软件的数据库。其中效果和简评需要保留html代码并修改成软件可渲染的html格式。

关键词:python 爬虫/web crawler BeautifulSoup4

二、思路分析
2.1 获取专栏中的166件圣物数据
方法一:最朴素的思想——复制粘贴
如果这个方法能简单解决问题,我也不会写这篇专栏了......
首先,很遗憾,直接复制粘贴并不能达到我期望的效果。因为我想要html代码的数据,这样在查询软件中也能渲染出加粗、颜色等文字效果。如果用F12+复制指定元素,需要我手动执行166*3次,人都点麻了。
其次,专栏内容的圣物不是完全按编号升序排列的,需要手动填入表格的对应编号行,费眼睛。可以输入单元格号来快速定位,但还是好麻烦。
方法二:网络爬虫
网络爬虫,是一种按照既定规则自动抓取网络数据或执行网络请求的程序,比较适合完成此类任务。之前浅学过一段时间,现在拿出来实际运用一下。
选用的策略是:获取html文档,喂给BeautifulSoup4(简称bs4)来解析,然后抽出想要的信息。重点就在于如何抽取所需信息——需要找出这些信息的特征,才能让bs4定位到信息所在标签tag,取出内容。

2.1.1 获取html文档
我所要的信息分布在10篇专栏中,访问它们的关键就在于CV号。

还好我有做过一篇目录专栏,通过它可以很方便地获取(爬a标签)到这些CV号。

使用requests库向发送html get请求后,10篇专栏的HTML文档就到手了!

2.1.2 使用bs4来解析html文档
Beautiful Soup是一个可以从HTML或XML文件中提取数据的Python库,它能够通过你喜欢的转换器实现惯用的文档导航,查找,修改文档的方式。
——BS4官方文档

接下来是为之后bs4定位“效果”“简评”“评分”tag做准备,先使用浏览器的“检查(inspect)”功能来查看各圣物专栏的tag特征。

首先,在检查后发现,专栏的内容部分在id=read-article-holder的div标签中,初步缩小了搜索范围。

其次,可以发现在div标签下含了无数的没有id的同级p标签。
由上图可见,编号,简评,评分都在p标签里,而周围都是同级的p标签,所以无法通过标签名、只能以标签内容来定位。
效果的内容都在ul列表标签内,专栏内使用此标签的也只有效果部分,可以作为标志来定位。

2.1.3 抽取信息
获取编号
只需要保留数字。对p标签内容使用正则表达式取数字或者用字符串split函数取第二段
获取效果
需要保留html代码。获取整个ul标签后使其变为字符串存储
获取简评
需要保留html代码。从简评标签之后到评分标签之前,获取连续的n个p标签后使其变为字符串存储
获取评分
需要保留普通字符串。获取去除“评分”和“空字符”后p标签内容。

2.2 效果、简评、评分按序录入已有excel

根据表头,定位效果、评论和分数列号。
如果是xls文件需要xlrd、xlwt库进行读写、xlutils库允许在原表格上追加修改
如果是xlsx文件只需要openpyxl库即可完成读写和修改
根据编号,定位行号。按行列定位单元格,依次输入对应圣物的效果、简评、评分内容

三、代码实现
2.0.0 导入包和模块

2.1.1 获取html文档

2.1.2 使用bs4来解析html文档

2.1.3~2.2 抽取信息+效果、简评、评分按序录入已有excel

2.3 测试代码

四、结果展示






五、实验反思
5.1 代码改进
以上代码似乎缺少了些面向对象的思想,这组功能围绕CV号、HTML文件和BS结构树展开,显然可以编成类,以进一步简化代码:
测试代码:

5.2 未来可能的改进
设置私有、只读属性/方法,保证数据安全。
增加容错处理,如HTML文档爬取失败的情况,调用后续函数需要检查是否初始化成功。
改进筛选函数,部分find_all可能多爬/少爬某些格式的信息,随着未来专栏的增多,可能会出现不可预期的错误,需要定期检查以及设置一些查看爬取信息的方法。

5.3 已解决的疑问
一开始,我的评分过滤器是从解析顺序着手的,用了BS遍历文档树-回退和前进的.next/previous_element方法:
但对第一篇专栏的提取数据却不符合预期:

于是我尝试查看这些无关标签满足筛选条件的原因:

问题得以迎刃而解:
字符串的find方法结果出现了None!而不是-1,所以通过了检测。原因是解析的结果不一定是字符串也有可能是tag,find方法接收到了非str类型的数据,返回了None
"评分标准"字符串的开头的确是评分二字,但不含有圣物信息,所以需要额外增加筛选条件,把此类信息滤掉

后来又改用get_text方法获取tag中包含的所有文本内容,还能去除tag间多余的空格,好用!

六、笔记总结
6.1 BeautifulSoup常用方法总结
6.1.1 筛选出指定条件的tag
find_all()方法
功能:搜索当前tag的所有子孙节点,并筛选出所有符合条件的tag,返回值为列表
语法:tag或BeautifulSoup对象.find_all(name, attrs, recursive, string, **kwargs)
简写:可以省略掉.find_all
参数:
name,表示tag名称,可以是字符串/正则表达式/列表/方法/True
attrs,表示特殊属性的tag,字典类型(本文没用到)
recursive,表示是否检索当前tag的所有子孙节点,布尔类型,默认为True,False表示只搜索tag的直接子节点
string,表示搜索字符串内容,可以是字符串/正则表达式/列表/方法/True
**kwargs,表示一堆关键字参数。其中,limit表示返回结果的最大数量,int类型;class_表示按照CSS类名搜索tag,可以是过滤器/字符串/正则表达式/方法/True;其他参数名表示搜索指定名字的属性,可以是字符串/正则表达式/列表/True
相似方法:find()方法,是find_all()方法的单数版本,有相同的形参,返回首个符合条件的tag
find_previous()方法
功能:搜索当前节点前符合条件的首个tag或字符串
语法:tag.find_previous(name, attrs, recursive, string, **kwargs)
参数:参考find_all()方法
相似方法:find_all_previous()方法,是find_previous()方法的复数版本,返回当前节点前所有符合条件的tag或字符串;而find_next(),find_all_next()是搜索当前节点后。它们都有相同的形参。
find_next_sibling()方法
功能:搜索当前节点后的所有兄弟节点,返回首个符合条件的tag
语法:tag或BeautifulSoup.find_next_sibling(name, attrs, recursive, string, **kwargs)
参数:参考find_all()方法
相似方法:find_next_siblings()方法,是find_next_sibling()方法的复数版本;而find_previous_sibling(),find_previous_siblings()是搜索当前节点前。它们都有相同的形参。

6.1.2 得到tag内的字符串
string方法
功能:对于仅含一段文本的tag或只有一个子标签且文本只出现在子标签间的tag,返回这段文本字符串
语法:tag.string
注意:tag包含多个子节点时,tag.string无法确定调用哪个子节点的内容,返回None
相似方法:strings方法,对于包含多个文本字符串的tag,返回一个包含所有文本字符串的生成器,可以使用for循环来获取其中的内容;stripped_strings方法,是去除空格、空行的strings版本。它们都不需要形参。
get_text()方法
功能:获取tag所含所有文本内容,包括子孙tag中的内容,返回合并后的Unicode字符串
语法:tag或BeautifulSoup对象.get_text(sep, strip=True)
参数:
sep表示相邻文本内容的分隔符,str类型
strip表示是否去除文本内容前后的空白符,布尔类型,True则去除

七、参考资料
1. BS4官方说明文档-中文版
https://beautifulsoup.readthedocs.io/zh_CN/v4.4.0/
2. xlsx文件的读写(openpyxl库的使用)
https://blog.csdn.net/liuyingying0418/article/details/101066630
3. xls文件的读写(xlrd/xlwt/xlutils库的使用)
https://cooc.cqmu.edu.cn/Course/KnowledgePoint/9389.aspx
4. 正则表达式的用法(re模块的使用)
https://www.cnblogs.com/huxi/archive/2010/07/04/1771073.html
5. BS4中string和text的区别
https://www.cnblogs.com/kaibindirver/p/11374669.html