beautifulsoup解析动态页面div未展开_Python爬虫 | 0xb 数据解析:PyQuery库

(给 抠腚男孩 加星标,提升Python、Android技能 )
作者:CoderPig
本节带来数据解析部分最后一个解析库PyQuery,它的API和前端著名框架jQuery相似,名字由此而来。 如果你有前端基础,用过jQuery,用PyQuery解析HTML是你绝佳选择。没用过也没关系,本节会详细讲解一波难点就是CSS选择器。 另外,如果完全没了解过CSS的朋友建议先翻阅前面写过的:Python爬虫 | 0x4 - Web基础  预热下。 官网文档

https://pyquery.readthedocs.io/en/latest/

320fd19059916a23d371f44c419f7433.png PyQuery基本使用 直接使用pip命令安装即可:
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 CSS选择器详解

// 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节点
---------------- 伪类选择器 CSS伪类用于指定要选择的元素的特殊状态,如:button:hover{}→ 指定用户将鼠标悬停到按钮时的样式。 主要分为:结构伪类选择器否定伪类选择器动态伪类选择器UI元素状态伪类选择器,PyQuery中主要用到前两个,一一简单介绍下: 1、结构伪类选择器:可根据文档结构关系来匹配特定节点,包括下述几种: Tips :结构伪类选择器中子节点的序号从1开始!!!?‍♂️
伪类示例描述
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节点,包括文本节点
这里childfo-type容易混淆,可以写个简单的代码验证下:
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 PyQuery DOM操作 所谓的Dom操作就是定位到特定节点,修改,删除或新增节点。不止PyQuery支持,之前学的lxml和BeautifulSoup也支持,顺带把PyQuery常用的函数也过下吧(更多的可自行查阅JQuery的API文档,毕竟类似~):
# 获取文本
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 0x4、实战:爬取穷游网日本城市及景点 爬取站点

https://place.qyer.com/japan/citylist-0-0-1/

爬取内容:爬取日本城市列表页里的城市及景点数据,保存到本地。

// 0x1、爬取分析 //

打开上述链接: a7997206d1eb1d8bff7f4907c6d477a7.png 红色部分就是要爬取的城市及景点信息,底部做了分页: 4c52443ee231a19d1db522b9af283964.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 不难写出这样的CSS选择器:.plcCitylist li e2fdfc16ff72cd109c57f435470edea3.png 可以,拿到li的列表,然后循环遍历,按需提取数据即可,比较简单,直接写出完整代码~

// 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 也可以打开cites.txt文件查看具体的数据: 39b9156163a051e5c8621074e75bfca1.png

- EOF -

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值