Scrapy新手求助:如何匹配非嵌套的关联HTML元素
嗨,作为刚上手Scrapy的新手,碰到这种H2和UL成对却不嵌套的情况,确实容易卡壳!我给你几个实用的解决思路,比正则处理要靠谱得多,完全符合Scrapy的最佳实践:
方法1:用XPath兄弟轴精准定位对应元素
这是最稳妥的方式,利用XPath的following-sibling轴,找到每个H2后面紧跟的第一个UL,天然实现二者的关联。
在你的Spider里可以这么写:
def parse(self, response): # 遍历页面中所有的H2元素 for h2 in response.xpath('//h2'): # 提取H2里的日期文本 date = h2.xpath('./text()').get().strip() # 定位当前H2后面的第一个UL元素(关键就在[1],确保只取相邻的那个) target_ul = h2.xpath('./following-sibling::ul[1]') # 遍历这个UL下的每个漏洞项 for li in target_ul.xpath('./li'): # 提取各个漏洞字段,这里根据你的页面结构调整XPath uid = li.xpath('./span[@class="vuln-uid"]/text()').get(default='') severity = li.xpath('./span[@class="severity"]/text()').get(default='') vuln_url = li.xpath('./a/@href').get(default='') description = li.xpath('./p[@class="desc"]/text()').get(default='') # 把日期和对应漏洞信息一起输出 yield { 'date': date, 'vuln_uid': uid, 'severity': severity, 'url': vuln_url, 'description': description }
这里的[1]是核心,它能保证我们只取当前H2后面第一个UL,不会误匹配到后面其他分组的UL元素。
方法2:用zip配对H2和UL列表(适合结构严格一一对应的场景)
如果页面里的H2和UL是严格成对出现、数量完全一致的,那可以分别提取所有H2和UL的列表,再用Python的zip函数把它们配对。
示例代码:
def parse(self, response): # 获取所有H2元素 h2_elements = response.xpath('//h2') # 获取所有和H2配对的UL(这里用preceding-sibling确保只取有对应H2的UL) ul_elements = response.xpath('//ul[preceding-sibling::h2]') # 遍历配对后的每组H2和UL for h2, ul in zip(h2_elements, ul_elements): date = h2.xpath('./text()').get().strip() for li in ul.xpath('./li'): # 同样提取漏洞字段 uid = li.xpath('./span[@class="vuln-uid"]/text()').get(default='') # ...其他字段提取 yield { 'date': date, 'vuln_uid': uid, # ...其他字段 }
这个方法简单直接,但要注意:如果页面存在多余的H2或UL,配对就会出错,所以只适合结构非常规整的页面。
方法3:基于公共父容器分组(优先推荐,如果页面有这种结构)
很多时候,成对的H2和UL其实会被同一个父容器包裹(比如一个<div>或者<section>),这时候先遍历父容器,再在容器内找H2和UL,是最不容易出错的方式。
比如页面结构是这样的:
<div class="vuln-group"> <h2>2024-05-20</h2> <ul> <li>...</li> </ul> </div> <div class="vuln-group"> <h2>2024-05-19</h2> <ul> <li>...</li> </ul> </div>
那你的Spider代码可以这么写:
def parse(self, response): # 遍历所有包含H2和UL的父容器 for group in response.xpath('//div[@class="vuln-group"]'): # 在容器内提取日期 date = group.xpath('./h2/text()').get().strip() # 在容器内提取UL下的漏洞项 for li in group.xpath('./ul/li'): uid = li.xpath('./span[@class="vuln-uid"]/text()').get(default='') # ...其他字段提取 yield { 'date': date, 'vuln_uid': uid, # ...其他字段 }
这种方法完全依赖页面的天然分组,是最稳定的方案,如果你的页面有这样的父容器,一定要优先用它!
总结一下,优先级是:找公共父容器 > 用XPath兄弟轴定位 > 用zip配对,这些方法都比正则处理HTML内容要优雅得多,也更符合Scrapy的设计理念。
内容的提问来源于stack exchange,提问作者John Dane
相关产品推荐
相关产品推荐

