用Xpath制作简单的爬取网页工具,获取神奇宝贝百科每只精灵的信息

news/2024/10/2 6:37:03

最近开始学习Python的爬虫应用,个人比较喜欢用Xpath的方式来爬取数据,今天就结合一下Xpath方式,以“神奇宝贝百科”为素材,制作一个爬取每只宝可梦数据的工程项目

准备工作
神奇宝贝百科地址:https://wiki.52poke.com/wiki/主页
工程项目的目标是,获取每只精灵的名字、编号、属性、特性、以及蛋群,比如这个妙蛙花页面的例子

我们可以先看看这个网站逻辑是怎么样的,妙蛙花的图鉴地址是这个:https://wiki.52poke.com/wiki/妙蛙花
和主页相比,wiki后面跟着的内容实际上是精灵的文本编码,那么我们就需要得到每只精灵的文本编码是多少了,而在神奇宝贝百科中,在全国图鉴页面中,刚好就可以查看全部宝可梦的名字

全国图鉴页面地址:https://wiki.52poke.com/wiki/宝可梦列表(按全国图鉴编号)
在这个页面里面,点击每只精灵的名字,就可以跳转到对应精灵的图鉴页面中去,也就是说这个页面里面,应该会包含每只宝可梦图鉴页面的超链接,我们可以用开发者模式看看

用元素查找器点击妙蛙种子的名字,查找到了这只宝可梦的超链接,点击进去,果然就是跳转到了妙蛙种子的图鉴页面了,这边爬虫的基本逻辑就是,先获取每只宝可梦的超链接,再逐个超链接来解析宝可梦的数据,并爬取获得

开始编码

#urllib中的URL拼接库
from urllib.parse import urljoin#导入request库
import requests#导入re库
import re#导入日志信息库
import logging#导入文件写入库
import json
from os import makedirs
from os.path import exists#导入lxml库
from lxml import etree#日志输出级别和输出格式
logging.basicConfig(level=logging.INFO,format='%(asctime)s-%(levelname)s:%(message)s')#打开文件夹results,如果没有,就创造一个这样的文件夹
RESULTS_DIR = 'results'
exists(RESULTS_DIR) or makedirs(RESULTS_DIR)

首先,导入一些基本的库,以方便我们使用对应的功能,由于最终的结果是导出JSON格式的文件,所以也导入了JSON的库
之后按照上一步的分析,把网址的根部URL,以及全国图鉴的URL以常量的方式作为一个常量编写到程序中,:

#当前站点的根目录
BASE_URL = 'https://wiki.52poke.com'
#测试用网站
GOAL_URL = 'https://wiki.52poke.com/wiki/%E5%AE%9D%E5%8F%AF%E6%A2%A6%E5%88%97%E8%A1%A8%EF%BC%88%E6%8C%89%E5%85%A8%E5%9B%BD%E5%9B%BE%E9%89%B4%E7%BC%96%E5%8F%B7%EF%BC%89'

第一步,先发送get请求,获取目标网页的代码,创建一个这样的函数来实现功能:

#抓取网页源代码函数
def scrape_page(url):#打印日志信息logging.info('scraping %s...',url)try:response = requests.get(url)if response.status_code == 200:#如果状态码是200,直接返回源代码,否则捕捉异常return response.textlogging.error('get invalid status code %s while scraping %s',response.status_code,url)except requests.RequestException:logging.error('error occurred while scraping %s',url,exc_info=True)

第二步,创建每只宝可梦的地址列表,我们可以通过解析全国图鉴的url,并根据起初观察的地址结构,来获得每只宝可梦的超链接

#获取每只宝可梦的网页代码函数
def parse_index(html):#将网页源码解析为lxml树tree = etree.HTML(html)#用lxml树来获取目标a节点的href属性elements = tree.xpath('//td[@class = "rdexn-name"]/a/@href')#如果没有数据,返回空列表,否则返回处理后的地址列表if not elements:return []for element in elements:#把根目录和href属性的值结合成每只宝可梦的页面地址detail_url = urljoin(BASE_URL,element)logging.info('get detail url %s',detail_url)yield detail_url


根据网页的逻辑,精灵的超链接都放在了class属性为rdexn-name的td节点下的a节点的href属性里面,所以这里用Xpath的结构//td[@class = "rdexn-name"]/a/@href来获取里面的值,通过urllib库下的urljion方法,把BASE_URL和href属性值合起来,就能得到每只宝可梦的页面链接,之后再以生成器对象返回

#抓取每只宝可梦的详细页面代码函数
def scrape_detail(url):return scrape_page(url)

创建一个专门抓取每只宝可梦详细页面的函数,这里直接调用之前解析网页的函数就可以了,之后就是抓取最重要的数据部分了,也是最复杂的一步

重点分析
第一步最简单,先创建一个解析精灵数据的函数,依然使用Xpath的对象树的方法来创建解析对象

#解析每只宝可梦的内容,包括属性、编号、名字、特性、蛋群
def parse_details(html):#将网页源码解析为lxml树tree = etree.HTML(html)

接下来就是重点环节了,要获取精灵的属性,就要观察它的网页结构是怎么样的:

每只宝可梦基本上有1~2种属性,根据网页结构,属性的路径表达可以是这样子:

#属性
types = tree.xpath('//table[@class = "roundy bgwhite fulltable"]//td[contains(@class,"roundy")]/a//span[@class = "type-box-9-text"]/text()')


编号就更简单了,直接找到title属性为“宝可梦列表(按全国图鉴编号)”的a标签,获取文本属性就可以了

#编号
number = tree.xpath('//a[@title = "宝可梦列表(按全国图鉴编号)"]/text()')


名字也很简单,直接获取style等于font-size:1.5em的span标签下的b标签的文本属性就可以了

#名字
name = tree.xpath('//span[@style = "font-size:1.5em"]/b/text()')

特性的话会相对比较麻烦,每只精灵至少有1种基本特性,而至多有两种基本特性,但是隐藏特性也属于精灵的特性,而且并不是每只精灵都会有隐藏特性的,例如铁甲蛹这只精灵


所以网页的结构也因此而不同,可以观察到,有隐藏特性和没隐藏特性的路径,是不一样的,没有隐藏特性的精灵,td的标签没有colspan这个属性,如果用同一种写法,可能会引起获取不到特性的问题


因此这就需要加上逻辑判断,根据情况来决定路径的执行,思路是根据上面td属性的区别来作出判断,找到这边标签的Xpath路径之后,作为if语句的判断条件,特性的路径语句也不难,找到包含“特性”字符的title属性就可以了

#特性
#如果没有隐藏特性
hide_feature = tree.xpath('/table[contians(@class,"roundy")]/tbody/tr[4]/td[@colspan = "2"]')
if not hide_feature:features = tree.xpath('//td/table[@class = "roundy bgwhite fulltable"]/tbody//a[contains(@title,"(特性)")]/text()')
else:features = tree.xpath('//td[@colspan = "2"]//a[contains(@title,"(特性)")]/text()')


蛋群属性也不难,按照页面逻辑,找到colspan属性的td标签下,包含“蛋群”字眼的title属性的a标签,获取文本就可以了

#蛋群
eggs = tree.xpath('//td[@colspan = "2"]//a[contains(@title,"(蛋群)")]/text()')

最后再将获取到的数据,以字典的形式返回,这样一来,就能得到一只宝可梦的属性、编号、名字、特性、蛋群的信息

return{'types':types,'number':number,'name':name,'features':features,'eggs':eggs}

这样的代码看似没问题,不过宝可梦有个设定就是,有些宝可梦可能会有其他形态,比如妙蛙花这只精灵,有超级进化,以及超极巨化的形态,那么要获取它的信息的时候,可能会出现数据重复的问题,这种情况是因为具有多种形态的宝可梦,网页结构不一样的原因


可以看到,妙蛙花因为具有多个形态的原因,网页结构上多了几个class属性为_toggle from的属性,那么就需要多做一步判断处理,这里只取他最基本的形态的数据为主,代码如下:

#解析每只宝可梦的内容,包括属性、编号、名字、特性、蛋群
def parse_details(html):#将网页源码解析为lxml树tree = etree.HTML(html)#判断是否有额外形态toggle = tree.xpath('//tr[@class = "_toggle form1"]')#根据是否有额外形态,来进行解析筛选if toggle:#属性types = tree.xpath('//tr[@class = "_toggle form1"]//table[@class = "roundy bgwhite fulltable"]//td[contains(@class,"roundy")]/a//span[@class = "type-box-9-text"]/text()')#编号number = tree.xpath('//tr[@class = "_toggle form1"]//a[@title = "宝可梦列表(按全国图鉴编号)"]/text()')#名字name = tree.xpath('//tr[@class = "_toggle form1"]//span[@style = "font-size:1.5em"]/b/text()')#特性#如果没有隐藏特性hide_feature = tree.xpath('/table[contians(@class,"roundy")]/tbody/tr[4]/td[@colspan = "2"]')if not hide_feature:features = tree.xpath('//tbody/tr[contains(@class,"_toggle form1")]//td//a[contains(@title,"(特性)")]/text()')else:features = tree.xpath('//tbody/tr[contains(@class,"_toggle form1")]//td[@colspan = "2"]//a[contains(@title,"(特性)")]/text()')#蛋群eggs = tree.xpath('//tr[@class = "_toggle form1"]//td[@colspan = "2"]//a[contains(@title,"(蛋群)")]/text()')else:#属性types = tree.xpath('//table[@class = "roundy bgwhite fulltable"]//td[contains(@class,"roundy")]/a//span[@class = "type-box-9-text"]/text()')#编号number = tree.xpath('//a[@title = "宝可梦列表(按全国图鉴编号)"]/text()')#名字name = tree.xpath('//span[@style = "font-size:1.5em"]/b/text()')#特性#如果没有隐藏特性hide_feature = tree.xpath('/table[contians(@class,"roundy")]/tbody/tr[4]/td[@colspan = "2"]')if not hide_feature:features = tree.xpath('//td/table[@class = "roundy bgwhite fulltable"]/tbody//a[contains(@title,"(特性)")]/text()')else:features = tree.xpath('//td[@colspan = "2"]//a[contains(@title,"(特性)")]/text()')#蛋群eggs = tree.xpath('//td[@colspan = "2"]//a[contains(@title,"(蛋群)")]/text()')return{'types':types,'number':number,'name':name,'features':features,'eggs':eggs}

最后一步是把数据导出为JSON文件格式

#保存为JSON格式
def save_data(data):name = data.get('name')data_path = f'{RESULTS_DIR}/{name}.json'json.dump(data,open(data_path,'w',encoding='utf-8'),ensure_ascii=False,indent=2)

所有的功能性代码就已经全部完成了,最后只需要执行主函数,让程序运行起来,就可以自动抓取每只宝可梦的数据了

#主函数
def main():#获取全国图鉴的页面信息html = scrape_page(GOAL_URL)#获取每只精灵的超链接代码detail_urls = parse_index(html)#把每只宝可梦的详情页逐个分析,用data的变量来存取for detail_url in detail_urls:detail_html = scrape_detail(detail_url)data = parse_details(detail_html)logging.info('get detail data %s',data)logging.info('saving data to json file')save_data(data)logging.info('data saved successfully')#运行程序
if __name__=='__main__':main()        

之后就可以获得每只宝可梦的JSON文件了,里面包括每只精灵的属性、编号、名字、特性、蛋群信息


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/44877.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

LeetCode 2268. Minimum Number of Keypresses

原题链接在这里:https://leetcode.com/problems/minimum-number-of-keypresses/description/ 题目: You have a keypad with 9 buttons, numbered from 1 to 9, each mapped to lowercase English letters. You can choose which characters each button is matched to as lo…

kafka事务流程

流程kafka事务使用的5个API // 1. 初始化事务 void initTransactions(); // 2. 开启事务 void beginTransaction() throws ProducerFencedException; // 3. 在事务内提交已经消费的偏移量(主要用于消费者) void sendOffsetsToTransaction(Map<TopicPartition,OffsetAndMetad…

nl80211

同 wpa_supplicant、hostapd 一样,nl80211 也可以管理无线网络,不同的是 wpa_supplicant 和 hostapd 是通过 nl80211 管理无线网络。Linux平台上目前常用的专门针对无线网络设备编程的API有两套最早的一套API由HP公司员工Jean Tourrilhes于1997年开发,全称为Linux Wireless …

C++11智能指针 unique_ptr、shared_ptr、weak_ptr、循环引用、定制删除器

目录智能指针场景引入 - 为什么需要智能指针?内存泄漏什么是内存泄漏内存泄漏的危害内存泄漏分类如何避免内存泄漏智能指针的使用及原理RAII简易例程智能指针的原理智能指针的拷贝问题智能指针的发展历史std::auto_ptr模拟实现auto_ptr例程:这种方案存在的问题:Boost库中的智能…

SingletonKit单例源码阅读学习

阅读学习QFramwork中的SingletonKit源码。 Singleton 普通类的单例 作为最常用的单例模块,通过继承单例泛型类来实现,需要私有构造;//使用第一种接口单例方式internal class Class2Singleton : Singleton<Class2Singleton>{//记录被初始化的次数private static int mI…

复习加总结

Markdown学习 标题 三级标题 四级标题 字体 粗体: 俩*hello,World 斜体:一个**hello,World* 斜体加粗三个* hello,World 删除线:两个~ hello,World 引用始作俑者没有受罚,仅仅是受害者再受害一次罢了,最多也就是管理/梦境支配者的人类在人类世界的走狗棋子被带走,毫无…

HandyControl 使用内置Command 执行无效问题

HandyControl 使用内置Command 执行无效问题HandyControl 中通过查阅代码HandyControl_Shared 共享项目中,Interactivity/Commands 目录下,存在着一些内置 Command,开心发现还有关闭窗体,最小化等系统级别常用命令。 CloseWindowCommand.cs ControlCommands.cs OpenLinkCom…

UE5——GAS实现连招的一种方案

前言 最近因为在研究多人联机同步下的动作同步,在Google上很幸运搜到了一篇日本博主写的GAS编写连招的方案,于是就打算贴出来分享一下,顺便讲讲实现的心得: 【UE5】GamePlayAbilitySystemによるコンボ攻撃の実装とそれに利用する小ネタ 前編【GAS】 【UE5】GamePlayAbilit…