(给
抠腚男孩
加星标,提升Python、Android技能
)
PyQuery基本使用
直接使用pip命令安装即可:
CSS选择器详解
我是class6 ,class1的子标签"class7">
我是class7 ,class6的子标签"class8">
我是class8 ,class7的子标签
----------------
⑨
:
伪类选择器
CSS伪类用于指定要选择的元素的特殊状态,如:button:hover{}→ 指定用户将鼠标悬停到按钮时的样式。
主要分为:结构伪类选择器、否定伪类选择器、动态伪类选择器和UI元素状态伪类选择器,PyQuery中主要用到前两个,一一简单介绍下:
1、结构伪类选择器:可根据文档结构关系来匹配特定节点,包括下述几种:
Tips
:结构伪类选择器中子节点的序号从1开始!!!?♂️
这里child和fo-type容易混淆,可以写个简单的代码验证下:
doc = pq(html) print(doc( 'p:nth-child(7)')) # 输出:
PyQuery DOM操作
所谓的Dom操作就是定位到特定节点,修改,删除或新增节点。不止PyQuery支持,之前学的lxml和BeautifulSoup也支持,顺带把PyQuery常用的函数也过下吧(更多的可自行查阅JQuery的API文档,毕竟类似~):
0x4、实战:爬取穷游网日本城市及景点
爬取站点:
红色部分就是要爬取的城市及景点信息,底部做了分页:
点击第二页,链接变成了:
不难写出这样的CSS选择器:.plcCitylist li
可以,拿到li的列表,然后循环遍历,按需提取数据即可,比较简单,直接写出完整代码~
也可以打开cites.txt文件查看具体的数据:
作者:CoderPig本节带来数据解析部分最后一个解析库PyQuery,它的API和前端著名框架jQuery相似,名字由此而来。 如果你有前端基础,用过jQuery,用PyQuery解析HTML是你绝佳选择。没用过也没关系,本节会详细讲解一波难点就是CSS选择器。 另外,如果完全没了解过CSS的朋友建议先翻阅前面写过的:Python爬虫 | 0x4 - Web基础 预热下。 官网文档:
https://pyquery.readthedocs.io/en/latest/
![320fd19059916a23d371f44c419f7433.png](https://i-blog.csdnimg.cn/blog_migrate/ea0afe556cdca8879deef677e58df971.png)
pip install pyquery
初始化的传参方式有三种:字符串,url,文件,示例如下:
import requests as rfrom pyquery import PyQuery as pq# 1) 传入字符串
resp = r.get("url")
doc_1 = pq(resp.text)# 2) 传入url,用得不多,一般用专门的网络库,如requests,可定制性较强些
doc_2 = pq(url='url', encoding='utf-8')# 3) 传入文件
doc_3 = pq(filename="/xxx/xxx.html")
Tips:PyQuery支持手动选择器解析器,构造时传参parse即可,默认使用lxml的xml解析器,一般不需要手动选择~?
常用函数如下:
PyQuery实例.(selector) → 查找所有子孙节点;
find(selector) → 查找所有子孙节点;
children(selector) → 查找所有子节点;
parent(selector) → 查找父节点;
parents(selector) → 查找祖先节点;
siblings(selector) → 查找兄弟结点;
items() → 得到一个生成器,遍历获得的对象都是PyQuery类型;
![320fd19059916a23d371f44c419f7433.png](https://i-blog.csdnimg.cn/blog_migrate/ea0afe556cdca8879deef677e58df971.png)
// 0x1、基础CSS选择器 //
三类:标签选择器,类选择器( → .xxx),id选择器(id="xxx" → #xxx,唯一!) 作用如下表所示:示例 | 作用 |
.container | 选择的所有节点 |
#info | 选择id="info"的所有节点 |
* | 选择所有节点 |
p | 选择所有p的节点 |
// 0x2、高级CSS选择器 //
除伪类选择器外,其他所谓的高级CSS选择器就是:普通CSS选择器 + 空格和符号,以不同的形式组合而已。通过代码加输出的方式帮助理解上手~ HTML样例代码:<section class="class1">
我是class_1
<p>我是class1里的p标签p>
<section class="class2">我是class2,class1的子标签section>
<section class="class3">我是class3,class1的子标签section>
<section class="class3">我是class3,class1的子标签section>
<section class="class4 class5">我是class4、class5,class1的子标签section>
<section class="class6">
我是class6,class1的子标签
<section class="class7">
我是class7,class6的子标签
<section class="class8">
我是class8,class7的子标签
<p>我是class8里的p标签p>section>
<p>我是class7里的p标签p>section>section>section>
----------------
①
:
相邻选择器
用+
隔开,如:.class2+div,定位到:的节点相邻的一个后续div节点。
<section class="class3">我是class3,class1的子标签section>
----------------
②
:
子选择器
用>
隔开,如:.class1>p,定位到:的节点的所有子节点中的div节点。
<p>我是class1里的p标签p>
----------------
③
:
后代选择器
用空格
隔开,如:.class1 p,定位到:的节点的所有子孙节点中的p节点。
<p>我是class1里的p标签p>
<p>我是class8里的p标签p>
<p>我是class7里的p标签p>
----------------
④
:
交集选择器
选择器间没有分隔,直接连接,如:div.class3,定位到div节点下且的节点。
<section class="class3">我是class3,class1的子标签section>
<section class="class3">我是class3,class1的子标签section>
<section class="class4 class5">我是class4、class5,class1的子标签section>
----------------
⑤
:
并集选择器
用,
隔开,如:.class2,.class3,定位到或的节点。
<section class="class2">我是class2,class1的子标签section>
<section class="class3">我是class3,class1的子标签section>
<section class="class3">我是class3,class1的子标签section>
----------------
⑥
:
相邻兄弟选择器
用+
隔开,如:.class2+div,定位到的节点后的一个相邻div结点。
<section class="class3">我是class3,class1的子标签section>
----------------
⑦
:
兄弟选择器
用~
隔开,如:.class3~div,定位到的节点后的所有兄弟结点中的div结点。
"class3">我是class3,class1的子标签
"class4 class5">我是class4
、class5
,class1的子标签"class6">
我是class6 ,class1的子标签"class7">
我是class7 ,class6的子标签"class8">
我是class8 ,class7的子标签
我是class8里的p标签
我是class7里的p标签
---------------- ⑧ : 属性选择器 有下述几种玩法:选择器 | 描述 |
a[attribute] | 带有attribute属性的a节点 |
a[attribute=value] | attribute属性等于value的a节点 |
a[attribute*=value] | attribute属性包含value的a节点 |
a[attribute^=value] | attribute属性以value开头的a节点 |
a[attribute$=value] | attribute属性以value结尾的a节点 |
伪类示例 | 描述 |
p:first-child | 选择父节点下的第一个子节点p |
p:last-child | 选择父节点下的最后一个子节点p |
p:nth-child(3) | 选择父节点下的第3个子节点p |
p:nth-last-child(3) | 选择父节点下的倒数第3个子节点p |
p:first-of-type | 选择父节点下所有p节点中的第一个 |
p:last-of-type | 选择父节点下所有p节点中的最后一个 |
p:nth-of-type(3) | 选择父节点下所有p节点中的顺数第3个 |
p:nth-last-of-type(3) | 选择父节点下所有p节点中的倒数第3个 |
p:only-child | 选择p节点,且父节点中仅有此p节点(只有一个) |
p:only-of-type | 选择p节点,且父节点中仅有此p节点(可以有其他类型的节点) |
p:empty | 没有子节点的p节点,包括文本节点 |
from pyquery import PyQuery as pq
html = """
第1个p
第2个p
第3个p
第4个p
第5个p
第1个span
第6个p
第7个p
第8个p
第2个span
第9个p
"""
doc = pq(html) print(doc( 'p:nth-child(7)')) # 输出:
第6个p
print(doc( 'p:nth-of-type(7)')) # 输出:第7个p
2、否定伪类选择器 :not(selector)→ 否定选择器,会过滤掉selector匹配的元素,比如:not(p)选择不为p节点的其他结点。 3、状态伪类选择器 匹配UI状态 → :enabled(可用的)、disabled(不可用的)、checked(选择的) 4、目标伪类选择器 匹配文档中uri中某个标志符的目标元素,:target。![320fd19059916a23d371f44c419f7433.png](https://i-blog.csdnimg.cn/blog_migrate/ea0afe556cdca8879deef677e58df971.png)
# 获取文本
a.text()# 获取属性
a.attr('href')# 获取标签内容
a.html()# DOM操作# ① class
li.removeClass('active') # 删除class
li.addClass('test') # 增加class
li.hasClass('id') # 判断是否有class# ② 属性(有则更新,没则新增)
a.css('id', 'myid')
a.attr('id', 'otherid')
a.remove_attr('id') # 删除属性# ③ 节点
a.append('新节点) # 新增节点
a.remove() # 移除节点
![320fd19059916a23d371f44c419f7433.png](https://i-blog.csdnimg.cn/blog_migrate/ea0afe556cdca8879deef677e58df971.png)
https://place.qyer.com/japan/citylist-0-0-1/
爬取内容:爬取日本城市列表页里的城市及景点数据,保存到本地。// 0x1、爬取分析 //
打开上述链接:![a7997206d1eb1d8bff7f4907c6d477a7.png](https://i-blog.csdnimg.cn/blog_migrate/7bf8a8acb16c00bf280999d5a7bc7ccf.png)
![4c52443ee231a19d1db522b9af283964.png](https://i-blog.csdnimg.cn/blog_migrate/f2b01f3bdfcbea129d559c20784d9ae4.png)
https://place.qyer.com/japan/citylist-0-0-2/
不难看出分页规律,就是替换最后一位数字,接着根据能爬取到的信息定义一个City类:class City:def __init__(self, order=-1, name_cn=None, name_en=None, cover_url=None, detail_url=None, visitor_count=0,
scenic_spot=None):self.order = order # 排序self.name_cn = name_cn # 城市名中文self.name_en = name_en # 城市名英文self.cover_url = cover_url # 封面URLself.detail_url = detail_url # 详情URLself.visitor_count = visitor_count # 多少人去过self.scenic_spot = scenic_spot # 景点def to_str(self):return "{}\n{}、{} - {}\n封面:{}\n详情:{}\n{}人去过\n景点:{}\n".format('-' * 64, self.order, self.name_cn, self.name_en, self.cover_url, self.detail_url, self.visitor_count,self.scenic_spot)
接着看下页面结构,写css选择器:
![325c0a81bd79f44620991937eb03b6a0.png](https://i-blog.csdnimg.cn/blog_migrate/137c0347dfb269987968e11419616496.png)
![e2fdfc16ff72cd109c57f435470edea3.png](https://i-blog.csdnimg.cn/blog_migrate/7396049080ea1d3ffa950e3bd0c2e59e.png)
// 0x2、完整代码实现 //
# -- coding: utf-8 --# !/usr/bin/env python"""------------------------------------------------- File : qyw_japan_city_spider.py Author : CoderPig date : 2020-12-10 8:14 Desc : 爬取穷游网日本城市及景点数据-------------------------------------------------"""import requests as rfrom pyquery import PyQuery as pqimport re
base_url = "https://place.qyer.com/japan/citylist-0-0-{}/"
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) ''Chrome/83.0.4103.97 Safari/537.36 '
}
count = 0 # 城市计数用
visitor_count_pattern = re.compile(r'(\d+)人去过')class City:def __init__(self, order=-1, name_cn=None, name_en=None, cover_url=None, detail_url=None, visitor_count=0,
scenic_spot=None):self.order = order # 排序self.name_cn = name_cn # 城市名中文self.name_en = name_en # 城市名英文self.cover_url = cover_url # 封面URLself.detail_url = detail_url # 详情URLself.visitor_count = visitor_count # 多少人去过self.scenic_spot = scenic_spot # 景点def to_str(self):return "{}\n {}、{} - {}\n 封面:{}\n 详情:{}\n {}人去过\n 景点:{}\n".format('-' * 64, self.order, self.name_cn, self.name_en, self.cover_url, self.detail_url, self.visitor_count,self.scenic_spot)def fetch_data(url):global count
resp = r.get(url, headers=headers)if resp is not None:if resp.status_code == 200:print("爬取:", resp.url)
doc = pq(resp.text)
li_s = doc('.plcCitylist li').items()
city_str_list = []for li in li_s:
city = City()
count += 1
city.order = count# 将封面缩略图替换为原图
city.cover_url = li('img').attr('src').replace('cover', '1360x900')
a = li('h3 a')
city.detail_url = 'https:' + a.attr('href')
city_split_list = a.text().split("\xa0\xa0")if city_split_list is not None:
city.name_cn = city_split_list[0]if len(city_split_list) > 1:
city.name_en = city_split_list[1]
count_group = visitor_count_pattern.search(li('p.beento').text())if count_group is not None:
city.visitor_count = count_group.group(1)
p_pois = li.find('p.pois')if len(p_pois) > 0:
city.scenic_spot = p_pois.text().replace(" ", "")else:
p_details = li('p.details')if len(p_details) > 0:
city.scenic_spot = p_details.text().replace(" ", "")
city_str_list.append(city.to_str())return "\n".join(city_str_list)else:print(resp.text)def write_to_file(content, file_path):with open(file_path, 'a+', encoding='utf-8') as f:
f.write(content)if __name__ == '__main__':for i in range(1, 110):
write_to_file(fetch_data(base_url.format(i)), 'cites.txt')print("{}个城市爬取完毕!".format(count))
运行后静待爬取完成,可以看到控制台输出的爬取信息:
![cbf4ecb509951c04970c4aef86152100.png](https://i-blog.csdnimg.cn/blog_migrate/85e8b10da84c968c831400f7b9bbf52e.png)
![39b9156163a051e5c8621074e75bfca1.png](https://i-blog.csdnimg.cn/blog_migrate/e781b0dae1b2325920a425cb4f4b98d3.png)
- EOF -