【那些反爬和反反爬】xpath根据兄弟节点定位元素、根据祖先节点排除标签、数据存储

简介: 【那些反爬和反反爬】xpath根据兄弟节点定位元素、根据祖先节点排除标签、数据存储

其实这篇不涉及什么高大上的反爬,但是实在不知道要把这篇文章归类到哪里,就直接先扔这里吧。

先吐槽一句:萌娘百科绝对是我再也不想爬第二次的网站。

第二句:(真理)把网站弄得越乱越让人摸不着头脑,就是最好的反爬手段,比什么加密都管用。

先看需求:爬取萌娘百科中所有游戏角色介绍

正常对于一个百科类网站而言,一拿到这个需求,第一反应肯定是先弄一个游戏角色名的list,然后挨个进行search,抓取返回的页面内容。然而当我想进一步索要游戏角色名的list时,得到的回复是:没有现成的游戏角色名,把萌娘上有的游戏角色爬了就行。

歪日???

一开始的思路还是先自己整一个游戏角色名的list,这样我面临一个很大的问题就是,量上不去。即便在度娘的帮助下,我能找到的游戏角色也就那么几个,再去除一些萌娘没有收录的词条,就所剩无几了。

没辙,死磕网站吧。

然后我找到了这个:https://mzh.moegirl.org.cn/Special:特殊页面

我一度以为自己发现了宝藏,直到我在让人眼花撩乱的页面中怎么也找不到我要的游戏角色分类…这分类都是啥啊,太乱了吧。

后来勉强又找到了两个看上去能爬的、勉强贴近我的需求的页面:https://mzh.moegirl.org.cn/Category:分类https://mzh.moegirl.org.cn/Category:电子游戏角色列表

可算是有点东西了。虽然这肯定不是所有的链接,但好歹能缓解我的燃眉之急。

这也算是给了一个新的思路:爬这种百科类的网站的时候,可以尝试去定位网站的根目录,然后从根目录里沿着网站的分类树一级一级地往下搜索,把需要的链接抓全

这是爬萌娘遇到的第一个问题,勉强算解决了吧。

爬的过程中发现,萌娘的每一个词条的最底端,会附上一大堆的相关或者参考链接,有很多游戏角色都会给总给在一个类似下边的大表中:

这张大表除了有游戏角色,还有一堆乱七八糟的分类,并且,每个页面下的大表可能都不一样,当然也有可能一样。

那我只要里边的角色名和链接,我该怎么操作?

观察页面源代码发现,表格的第一列(即带有xx角色字样的标题列)跟其右边的内容是一一对应的,同在一个tr标签下,也就是说,他们是兄弟节点。

XPath的following-sibling轴可以选择当前节点之后的兄弟节点:

from lxml import etree
# 使用XPath选择包含"角色"文字的td标签,并获取其下一个兄弟节点的内容
selected_elements = tree.xpath('//td[contains(text(), "角色")]/following-sibling::td[1]')

在XPath表达式中,//td[contains(text(), "角色")]/following-sibling::td[1]表示选择包含"角色"文字的td标签,然后获取其下一个兄弟节点的内容。

最终的代码如下:

def get_table_links(url):
    headers = {
        'User-Agent': random.choice(USER_AGENTS_LIST),
        'Referer': url
    }
    resp = requests.get(url, headers=headers)
    text = resp.text
    html = etree.HTML(text)
    # 所有罗列了游戏角色的表格定位
    hrefs = html.xpath('(//table[@class="navbox"])[1]//td[contains(text(), "角色")]/following-sibling::td[1]//a[not(@class="new") and not(starts-with(@href, "http"))]/@href')
    return hrefs

其中,//a[not(@class="new") and not(starts-with(@href, "http"))]意味获取所有class不为“new”的a标签下的链接,之所以要排除掉这个class,是因为萌娘中,class="new"意味着该页面不存在。

最后感慨,我确实、十分、非常佩服萌娘网站的设计者,顶礼膜拜的那种。

补充,抓网页内容的时候,我需要抓取网页中的div下的所有文本,但是总抓到一些乱七八糟的css代码、js代码之类的东西,一查看,是萌娘中的一些字体采用了加粗、颜色等样式修饰,有些还设置了不同的显示模式(比如鼠标停留在抹黑的区域才给显示文字这种,挺有意思的)。反应岛页面源代码中,就是p标签下的文字后边跟着一个style标签或者script标签,类似下边这种:

<p>
在这个世界中,「崩坏」是一种
<span>周期性
<style>.vue3-marquee{display:flex!important;position:relative}.vue3-marquee.horizontal{overflow-x:hidden!important;flex-direction:row!important;</style>
</span>
灾难,它伴随文明而生,以毁灭文明为最终目标。 
</p>

直接用xpath抓取p标签下的所有文本tree.xpath('//div[@class="mw-parser-output"]//text(),会把style或者script标签下乱七八糟的那一堆东西也抓到(一开始用这个跑了几个页面发现总是能抓到我不想要的那一堆东西),查了下网上的资料,根据xpath语法在后边加一个not把不想要的标签过滤掉就可以:

selected_elements = html.xpath('//div[@class="mw-parser-output"]//text()[not(ancestor::style) and not(ancestor::script)]') 

这里总结一下,following-sibling找的是兄弟节点,一般把这个写在xpath的路径下,用于路径的定位;ancestor找的是祖先节点(直译hhhh),可以放在xpath表达式最后,用于对不相关标签的一个清洗。另外,xpath中的notand语句可以跟@class@href结合使用,根据(比如a)标签内的属性对标签进行过滤和筛选

最后再补充个题外话,所有的数据都是为后边的算法服务的,所以数据存储的时候必须考虑到后续代码读取数据是否方便,是否可以写一段通用的代码读取所有的数据。所以在存数据的时候,如果暂时不写入数据库的话,最好能按行存成txt或者jsonl文件格式,具体哪个看数据的复杂程度,简单存个链接或者关键字啥的用txt就可以了,字段比较复杂的存成jsonl。特别是对于爬虫数据来说,不要轻易存json文件!因为当内容太多写入的文件太大的时候(上限是啥我也没试过,盲猜取决于你的电脑运行内存),json文件无法一次性被loads进python直接进行读取,程序会报错的,导致最后还是得按行读取json文件,然后就是被各种奇奇怪怪的换行符和左右括号支配的恐惧,最后还得重新爬(如果你有别的解决方案欢迎踢我)。

以萌娘为例,记录一下我们讨论数据存储格式和要保留的字段的大致经过:

第一版:

{
"title":页面总标题,
"目录1":内容1,
"目录2":内容2,
...
}

否了,萌娘的每个页面的目录又不是完全一样,这样后边没法读取。

第二版:

{
"title":页面总标题,
"content":{
    "目录1":内容1,
    "目录2":内容2,
    ...
    },
}

这个倒还能看得过去(事实上最后敲定的就是这一版),但是我总觉得还是差点啥。

自己再改的第三版:

{
"title":页面总标题,
"content":{
    "category":{
    1:"目录1",
    2:"目录2",
    ...
    }
    "目录1":内容1,
    "目录2":内容2,
    ...
  },
}

这个可以通过"category"先定位到各条数据的目录,然后进一步定位内容。但是即使不加"category",for循环也可以直接遍历key先找到各个目录然后进一步定位的,多不了几行代码。把目录单独再存一下,可能就是拿空间换时间吧。

最后,为了方便数据追溯,再补充一些其他字段:

{
"title":页面总标题,
"content":{
    "category":{
    1:"目录1",
    2:"目录2",
    ...
    }
    "目录1":内容1,
    "目录2":内容2
    ...
  },
"url":当前链接,
"referer_title":上一级标题,
"referer":上一级链接,
}


目录
相关文章
|
5天前
|
JavaScript
js 解析和操作树 —— 获取树的深度、提取并统计树的所有的节点和叶子节点、添加节点、修改节点、删除节点
js 解析和操作树 —— 获取树的深度、提取并统计树的所有的节点和叶子节点、添加节点、修改节点、删除节点
10 0
|
2月前
|
人工智能 算法 BI
【深度优先搜索】【树】【图论】2973. 树中每个节点放置的金币数目
【深度优先搜索】【树】【图论】2973. 树中每个节点放置的金币数目
|
2月前
|
测试技术
【树】【图论】【树路径】【深度优先搜索】2867. 统计树中的合法路径数目
【树】【图论】【树路径】【深度优先搜索】2867. 统计树中的合法路径数目
|
2月前
leetcode-6134:找到离给定两个节点最近的节点
leetcode-6134:找到离给定两个节点最近的节点
25 0
|
11月前
|
算法
算法训练Day18|● 110.平衡二叉树 ● 257. 二叉树的所有路径 ● 404.左叶子之和
算法训练Day18|● 110.平衡二叉树 ● 257. 二叉树的所有路径 ● 404.左叶子之和
剑指offer 76. 树中两个结点的最低公共祖先
剑指offer 76. 树中两个结点的最低公共祖先
53 0
|
算法
【算法真题 一】满二叉搜索树求三个节点的最低公共祖先
【算法真题 一】满二叉搜索树求三个节点的最低公共祖先
75 0
|
C语言
labview节点公式节点反馈节点表达节点属性节点
labview节点公式节点反馈节点表达节点属性节点
202 0
JavaWeb - 查询任意节点的所有子节点(包括孙子节点)
JavaWeb - 查询任意节点的所有子节点(包括孙子节点)
303 0
给定一棵树,树中包含N个节点(编号1-n) ,和n-1 无向边
请你找出树的重心,并输出将重心删除,剩余各个连通块中点数的最大数值
144 0