wjx-2005-07-01  

解析库

解析库的意思是对我们需要爬取的数据进行操作,从而获得想要的数据。

解析库有很多种,我们可以根据自己的习惯进行选取。

主要分为以下四种:

  • 正则表达式
  • xpath
  • Beautiful Soup
  • pyquery

一、正则表达式

首先讲解正则表达式的使用方法.

1) 元字符

元字符 匹配内容
. 匹配除换行符以外的任意字符
\w 匹配所有普通字符(数字、字母或下划线)
\s 匹配任意的空白符
\d 匹配数字
\n 匹配一个换行符
\t 匹配一个制表符
\b 匹配一个单词的结尾
^ 匹配字符串的开始位置
$ 匹配字符串的结尾位置
\W 匹配非字母或数字或下划线
\D 匹配非数字
\S 匹配非空白符
a|b 匹配字符 a 或字符 b
() 正则表达式分组所用符号,匹配括号内的表达式,表示一个组。
[...] 匹配字符组中的字符
[^...] 匹配除了字符组中字符的所有字符

2) 量词

量词 用法说明
* 重复零次或者更多次
+ 重复一次或者更多次
重复0次或者一次
重复n次
重复n次或者更多次
重复n到m次

3) 字符组

有时也会出现各种字符组成的字符组,这在正则表达式中使用[]

正则 待匹配字符 匹配结果 说明
[0123456789] 8 True 在一个字符组里枚举所有字符,字符组里的任意一个字符 和"待匹配字符"相同都视为可以匹配。
[0123456789] a False 由于字符组中没有 "a" 字符,所以不能匹配。
[0-9] 7 True 也可以用-表示范围,[0-9] 就和 [0123456789] 是一个意思。
[a-z] s True 同样的如果要匹配所有的小写字母,直接用 [a-z] 就可以表示。
[A-Z] B True [A-Z] 就表示所有的大写字母。
[0-9a-fA-F] e True 可以匹配数字,大小写形式的 a~f,用来验证十六进制字符。

1、match方法

该方法用于指定文本模式和待匹配的字符串。第一个参数表示文本模式,第二个参数表示待匹配的字符串。

如果匹配成功,就可以调用group方法获取匹配成功的字符串。

import  re
m = re.match('hello','hello')
if m is not None:
    print(m.group())            #使用group方法
print(m.__class__.__name__)     #输出m的类名
m = re.match('hello','hello world')     #只要模式从字符串起始位置开始,也可以匹配成功
if m is not None:
    print(m.group())
print(m)
hello
Match
hello
<re.Match object; span=(0, 5), match='hello'>

2、search方法

搜索是正则表达式的另一类常用的应用场景。也就是从一段文本找到一个或多个与文本模式相匹配的字符串。

import re
m = re.match('python','I love python')
print(m)
m = re.search('python','I love python')
print(m)
s = '.ind'
m = re.match(s,'bind')
if m is not None:
    print(m.group())
print(m)
m = re.match(s,'bin')
print(m)
None
<re.Match object; span=(7, 13), match='python'>
bind
<re.Match object; span=(0, 4), match='bind'>
None

3、分组

如果一个模式字符串中有一对圆括号括起来的部分,那么这部分就会作为一组,可以通过group方法的参数获取指定的组匹配的字符串。在我们匹配一些需要抓取的信息时,往往会在一次模式匹配时,就获取很多想要的信息,那么此时,我们就需要将想要获取的信息用括号括起来,就可以形成一个列表,这个列表里面的内容就都是我们想要的内容。

import re
m = re.match('(\d{3})-(\d{4})-([a-z]{2})','123-4567-xy')
if m is not None:
    print(m.group())
    print(m.group(1))
    print(m.group(2))
    print(m.group(3))
    print(m.groups())
print("========================")
123-4567-xy
123
4567
xy
('123', '4567', 'xy')
=======================

4、findall和finditer方法

findall函数用于查询字符串中某个正则表达式模式全部的非重复出现情况,finditer方法与findall方法相似,只不过使用finditer方法需要进行迭代,这样更节省内存。

import re
s = '12-a-abc54-a-xyz---78-A-ytr'
#r是规避转义的字符
result = re.findall(r'\d\d-a-[a-z]{3}',s)
#匹配出了两个与该模式字符串相匹配的字符串
print(result)
['12-a-abc', '54-a-xyz']

5、sub和subn方法

sub函数与subn函数用于实现搜索和替换功能。这两个函数功能几乎完全一样,只不过sub函数返回替换后的结果,subn函数返回一个元组,元组的第一个元素是替换后的结果,第二个元素是替换的总数。

import re
#使用sub函数,第一个参数是模式字符串,第二个参数是要替换的字符串,第三个参数是被替换的字符串
result = re.sub('Mike','Bill','Mike is my son,I like Mike')
print(result)
result = re.subn('Mike','Bill','Mike is my son,I like Mike')
print(result)
#运行结果
Bill is my son,I like Bill
#元组
('Bill is my son,I like Bill', 2)

6、split方法

split函数用于根据正则表达式分隔字符串,split函数的第一个参数是模式字符串,第二个参数是带分隔的字符串。

import re
result = re.split(';','Bill;Mike;John')
print(result)
#运行结果
['Bill', 'Mike', 'John']
实战案例:抓取猫眼电影TOP100榜单(http://www.maoyan.com/)
import requests
#导入正则表达式
import re
#导入会话,维持状态
from requests import Session
session = Session()
class CatEyeMovie:
    def __init__(self):
        """
        个人爬取网站的一些经验:
        一开始我只使用了UA最为代理,后来发现爬取一段时间后,就不能在进行爬取了。
        然后我就导入了Host和Referer两个请求头作为参数,然后又爬取一段时间后又不能
        爬取了,然后我有采用了cookie值,并导入了session维持会话,后来保持稳定了,但
        我也不能完全保证后面不会出现问题,还有一个需要大家注意的点,就是我们最好将UA
        经常换一下,否则网站很容易判断你就是一个爬虫。
        """
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1',
            'Host':'www.maoyan.com',
            'Referer':'https: // www.maoyan.com / board / 6?timeStamp = 1705477546939 & channelId = 40011 & index = 1 & signKey = 53785d6920acbc1fad61a808070e6544 & sVersion = 1 & webdriver = false',
            'Cookie':'__mta = 208959021.1705475089718.1705477531026.1705477547761.23;uuid_n_v = v1;uuid = AEE07180B50611EE8FD01FDE53D00FC1F4728774816242469EDC18C661A68E3E;_csrf = bd11e59bd2541a5db1defecd0b98453acc792bc7b43e8c627d9f291922d330a5;Hm_lvt_703e94591e87be68cc8da0da7cbd0be2 = 1705475088;_lx_utm = utm_source % 3DBaidu % 26utm_medium % 3Dorganic;_lxsdk_cuid = 18d163ca06e88 - 09e6b05cd12e1e - 26001951 - 1bcab9 - 18d163ca06fc8;WEBDFPID = z7vx1480u69z54z2y8wxxw25u87w9wy681w834vxv2w9795839872754 - 2020837336064 - 1705477335287GSOGGSIfd79fef3d01d5e9aadc18ccd4d0c95079584;token = AgFrJIp1VufADHtEUTi0mp6KitQUBV_Ejob2I8Ux0OAvh6hc4y2_WhnCuFWgooIo_KncuV_jDf4ttQAAAABjHQAAZVmX7tI2iKUXgdLslHWY_dwRCQzD5i7IK_Ayog8 - 7acQiKH6CO0VVeeAwm4LSd1d;uid = 4274654873;uid.sig = vNE7mg6CHWQbb4imGsjuWSza8rs;lxsdk = AEE07180B50611EE8FD01FDE53D00FC1F4728774816242469EDC18C661A68E3E;__mta = 208959021.1705475089718.1705477312976.1705477544324.6;Hm_lpvt_703e94591e87be68cc8da0da7cbd0be2 = 1705477548;_lxsdk_s = 18d163ca06f - 4af - c1e - 68b % 7C % 7C62'
        }
    def get_html(self):
        #通过分析网站的URL,我们可以得出网站地址的规律
        for i in range(0,101,10):
            url = f'https://www.maoyan.com/board/4?timeStamp=1705475173402&channelId=40011&index=10&signKey=31588cad6150fc9509dd3f73c509fdac&sVersion=1&webdriver=false&offset={i}'
            response = session.get(url,headers=self.headers)
            #作判断,如果不是200,表示爬取不了
            if response.status_code == 200:
                html = response.text
                #print(html)
                self.re_html(html)
    def re_html(self,html):
       	#使用正则表达式获取电影名和主演
        re_act = '<p\sclass="name">.*?>(.*?)</a></p>\n.*?<p\s.*?>\n(.*?)\n.*?</p>'
        #通过正则表达式获取图片链接
        re_img_src = '<img\sdata-src="(.*?)".*?/>'
        lists = re.findall(re_act,html,re.S)
        img_lists = re.findall(re_img_src,html,re.S)
        for list,img_list in zip(lists,img_lists):
            title = list[0]
            #使用strip函数去掉字符串两头的空格
            lead_act = list[1].strip()
            print(title)
            print(lead_act)
            print(img_list)
            print("==================")


if __name__ == '__main__':
    cat = CatEyeMovie()
    cat.get_html()
我不是药神
主演:徐峥,周一围,王传君
https://p0.pipi.cn/mmdb/d2dad59253751bd236338fa5bd5a27c710413.jpg?imageView2/1/w/160/h/220
==================
肖申克的救赎
主演:蒂姆·罗宾斯,摩根·弗里曼,鲍勃·冈顿
https://p0.pipi.cn/mmdb/fb7386020fa51b0fafcf3e2e3a0bbe694d17d.jpg?imageView2/1/w/160/h/220
==================
海上钢琴师
主演:蒂姆·罗斯,比尔·努恩 ,克兰伦斯·威廉姆斯三世
https://p0.pipi.cn/mmdb/d2dad592c7e7e1d2365bf1b63cd25951b722b.jpg?imageView2/1/w/160/h/220
==================
绿皮书
主演:维果·莫腾森,马赫沙拉·阿里,琳达·卡德里尼
https://p0.pipi.cn/mmdb/d2dad59253751b230f21f0818a5bfd4d8679c.jpg?imageView2/1/w/160/h/220
==================
霸王别姬
主演:张国荣,张丰毅,巩俐
https://p0.pipi.cn/mmdb/fb7386beddd338537c8ea3bb80d25a9078b13.jpg?imageView2/1/w/160/h/220
==================
美丽人生
主演:罗伯托·贝尼尼,朱斯蒂诺·杜拉诺,赛尔乔·比尼·布斯特里克
https://p0.pipi.cn/mmdb/d2dad592c7e7e1d2367a3507befaed31a5903.jpg?imageView2/1/w/160/h/220
==================
这个杀手不太冷
主演:让·雷诺,加里·奥德曼,娜塔莉·波特曼
https://p0.pipi.cn/mmdb/d2dad592c7e7e13ba3ddd25677b4d70fc45fa.jpg?imageView2/1/w/160/h/220
==================
小偷家族
主演:中川雅也,安藤樱,松冈茉优
https://p0.pipi.cn/mmdb/d2dad5925372ffd7c387a9d01bddad81625c3.jpg?imageView2/1/w/160/h/220
==================
星际穿越
主演:马修·麦康纳,安妮·海瑟薇,杰西卡·查斯坦
https://p0.pipi.cn/mmdb/d2dad592b125bfc9fd300b8a46169f8008efb.jpg?imageView2/1/w/160/h/220
==================
怦然心动
主演:玛德琳·卡罗尔,卡兰·麦克奥利菲,艾丹·奎因
https://p0.pipi.cn/mmdb/d2dad592b122ff8d3387a93ccab6036f616c1.jpg?imageView2/1/w/160/h/220
==================
盗梦空间
主演:莱昂纳多·迪卡普里奥,渡边谦,约瑟夫·高登-莱维特
https://p0.pipi.cn/mmdb/fb7386510fa2ffd7c3e19bcb2e6ea4dc44460.jpg?imageView2/1/w/160/h/220
==================
泰坦尼克号
主演:莱昂纳多·迪卡普里奥,凯特·温丝莱特,比利·赞恩
https://p0.pipi.cn/mmdb/fb73869af2a51b339e537c802afb40db7a7fe.jpg?imageView2/1/w/160/h/220
==================
哪吒之魔童降世
主演:吕艳婷,囧森瑟夫,瀚墨
https://p0.pipi.cn/mmdb/d2dad592537923f0ee07acada3ac59b9f3ffb.jpg?imageView2/1/w/160/h/220
==================

二、lxml和xpath

1、lxml基础

lxml是python的一个解析库,用于解析HTML和XML,支持xpath解析方式。由于lxml底层是使用C语言编写的,所以解析效率非常高。但是lxml插件需要安转,在这里,我就不讲如何安装,大家可以用自己的方法去安装。

2、操作xml

lxml可以读取XML文件,也可以使用字符串形式的XML文档。读取XML文件时,需要使用parse函数,该函数需要传入XML文件名。如果要解析字符串形式的XML文档,需要使用fromstring函数,该函数的参数就是XML字符串。

<products>
    <product id = "0001">
        <name>手机</name>
        <price>2000</price>
    </product>
    <product id = "0002">
        <name>电脑</name>
        <price>8350</price>
    </product>
</products>
from lxml import etree
tree = etree.parse('product.xml')
print(type(tree))
#将tree重新转换为字符串形式的XML文档
print(str(etree.tostring(tree,encoding="utf-8"),'utf-8'))
#获得根结点对象
root = tree.getroot()
#输出根结点的名称
print(root.tag)
#获得根结点的所有子节点
children = root.getchildren()
for child in children:
    #获取子节点的ID属性值
    print(child.get('id'))
    #获取子节点的文本内容
    print(child[0].text)
    print(child[1].text)
#重写XML文档
root = etree.fromstring(
    '''
    <product>
            <product1 name="iphone"/>
            <product2 name="iPad"/>
    </product>
    '''
)
#注意:从这里root的值就开始改变了,不在是XML文件里的值,而是自己写的
print(root.tag)
children = root.getchildren()
for child in children:
    print(child.get('name'))
<class 'lxml.etree._ElementTree'>
<products>
    <product id="0001">
        <name>手机</name>
        <price>2000</price>
    </product>
    <product id="0002">
        <name>电脑</name>
        <price>8350</price>
    </product>
</products>
products
0001
手机
2000
0002
电脑
8350
product
iphone
iPad

3、操作HTML

使用lxml库解析HTML与解析XML类似,在这里,我通过代码能够让读者更加容易理解。

from lxml import etree
#创建lxml.etree.HTMLPareser对象
parser = etree.HTMLParser()
print(type(parser))
#读取并解析html文件
tree = etree.parse('product.html',parser)
#获取根结点
root = tree.getroot()
#将html文档转换为可读格式
result = etree.tostring(root,encoding='utf-8',pretty_print=True,method="html")
print(result,'utf-8')
#获取根结点名称
print(root.tag)
print(root.get('lang'))
#在这里相当于列表,head是列表里的第一个值,body是列表的第二个值
print(root[0][0].get('charset'))
print(root[0][1].text)
print(root[1][0].text)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>这是一个HTML文件</title>
</head>
<body>
    <h1>解析库</h1>
</body>
</html>
<class 'lxml.etree.HTMLParser'>
b'<html lang="en">\r\n<head>\r\n    <meta charset="UTF-8">\r\n    <title>\xe8\xbf\x99\xe6\x98\xaf\xe4\xb8\x80\xe4\xb8\xaaHTML\xe6\x96\x87\xe4\xbb\xb6</title>\r\n</head>\r\n<body>\r\n    <h1>\xe8\xa7\xa3\xe6\x9e\x90\xe5\xba\x93</h1>\r\n</body>\r\n</html>\n' utf-8
html
en
UTF-8
这是一个HTML文件
解析库

4、Xpath

1) 基本语法使用

Xpath 使用路径表达式在文档中选取节点,下表列出了常用的表达式规则:

表达式 描述
node_name 选取此节点的所有子节点。
/ 绝对路径匹配,从根节点选取。
// 相对路径匹配,从所有节点中查找当前选择的节点,包括子节点和后代节点,其第一个 / 表示根节点。
. 选取当前节点。
.. 选取当前节点的父节点。
@ 选取属性值,通过属性值选取数据。常用元素属性有 @id 、@name、@type、@class、@tittle、@href。

2) xpath通配符

Xpath 表达式的通配符可以用来选取未知的节点元素,基本语法如下:

通配符 描述说明
* 匹配任意元素节点
@* 匹配任意属性节点
node() 匹配任意类型的节点

xpath其它知识点

1、可以使用“|”,表示多路径匹配,有时获取相同的位置格式,解析语法并不是完全一样的。

2、xpath还有很多内建函数,在这里我说一个经常用的就是“text属性”,后面进行实战案例时,会给大家详细介绍。

想要了解更多关于 Xpath 的知识可访问官方网站:https://www.w3.org/TR/xpath/

实战案例:抓取起点中文网小说信息()
#导入Excel表格
import xlwt
import requests
#导入随机函数,可以使用随机UA代理池
import random
from lxml import etree
#这里面是一些UA代理,可以随机切换UA,避免浏览器发现爬虫程序
from UA_info import ua_list
class StartSpider:
    #定义表头
    header = ['书名', '作者', '类型', '完成度', '介绍']
    #创建workbook对象
    book = xlwt.Workbook(encoding='utf-8')
    #添加一个名为novels的sheet
    sheet = book.add_sheet('novels')
    #为Excel表单添加表头
    for h in range(len(header)):
        sheet.write(0, h, header[h])
    def __init__(self):
        #计数器
        self.i = 1
        self.headers = {
            'User-Agent': random.choice(ua_list),
        }
    def get_html(self):
        for i in range(1,6):
            url = f'https://www.qidian.com/all/page{i}/'
            response = requests.get(url,headers=self.headers)
            if response.status_code == 200:
                html = response.text
                self.xml_html(html)
    def xml_html(self,html):
        xml = etree.HTML(html)
        #书名的解析语法
        title_list = xml.xpath('//*[@id="book-img-text"]/ul/li/div[2]/h2/a/text()')
        #作者名的解析语法
        author_list = xml.xpath('//*[@id="book-img-text"]/ul/li/div[2]/p[1]/a[1]/text()')
        #小说类型的解析语法
        type_list1 = xml.xpath('//ul[@class="all-img-list cf"]//p/a[2]/text()')
        type_list2 = xml.xpath('//ul[@class="all-img-list cf"]//p/a[3]/text()')
        #小说完成度解析语法
        complete_list = xml.xpath('///ul[@class="all-img-list cf"]//p[@class="author"]/span/text()')
        #小说简介
        introduce_list = xml.xpath('//ul[@class="all-img-list cf"]//p[@class="intro"]/text()')
        for title,author,type1,type2,complete,introduce in zip(title_list,author_list,type_list1,type_list2,complete_list,introduce_list):
            type = type1+'·'+type2
            print(title)
            print(author)
            print("========================")
            self.save_data(title,author,type,complete,introduce)
    #定义保存数据的函数
    def save_data(self,title,author,type,complete,introduce):
        self.sheet.write(self.i,0,title)
        self.sheet.write(self.i,1,author)
        self.sheet.write(self.i,2,type)
        self.sheet.write(self.i,3,complete)
        self.sheet.write(self.i,4,introduce)
        self.i += 1
if __name__ == '__main__':
    start = StartSpider()
    start.get_html()
    #将内存中的Excel数据保存为该文件
    StartSpider.book.save('novels.xls')
灵境行者
卖报小郎君
========================
赤心巡天
情何以甚
========================
宿命之环
爱潜水的乌贼
========================
都重生了谁谈恋爱啊
错哪儿了
========================
深海余烬
远瞳
========================
谁让他修仙的!
最白的乌鸦
========================
这游戏也太真实了
晨星LL
========================
万古神帝
飞天鱼
========================
逼我重生是吧
幼儿园一把手
========================
玄鉴仙族
季越人
========================

1705486991290

以上是写入Excel表格中的数据!

三、Beautiful Soup库

由于Beautiful Soup提供了多种选择器,所以使用起来不仅比XPATH方便,而且更灵活。
注意:在使用Beautiful Soup时,需要指定解析器,一般使用lxml解析器,这个解析器非常好用

1、节点选择器

节点选择器直接通过节点的名称选取节点,然后再用string属性就可以得到节点内的文本

from bs4 import BeautifulSoup
html = '''
<html lang="en">
<head>
   <meta charset="UTF-8">
   <title>Beautiful Soup演示</title>
</head>
<body>
   <div>
       <ul>
           <li class="item1" value1="1234" value2="hello world">
               <a href="http://www.baidu.com">baidu.com</a>
           </li>
           <li class="item2">
               <a href="https://www.jd.com">京东商城</a>
           </li>
           <li class="item3">
               <a href="https://www.taobao.com">淘宝</a>
           </li>
       </ul>
   </div>
</body>
</html>
'''
#创建BeautifulSoup对象,并通过BeautifulSoup类的第二个参数指定lxml解析器
soup = BeautifulSoup(html,'lxml')
#获取title节点的名称
print(soup.title.name)
#获取第一个li节点所有属性名和属性值
print(soup.li.attrs)
#获取第一个li节点value2属性的值
print(soup.li.attrs["value2"])
#获取第一个li节点value1属性的值
print(soup.li['value1'])
#获取第一个a节点的href属性的值
print(soup.a['href'])
#获取第一个a标签的文本内容
print(soup.a.string)

title
{'class': ['item1'], 'value1': '1234', 'value2': 'hello world'}
hello world
1234
http://www.baidu.com
baidu.com

在使用节点选择器时,并不是一次能够将需要的节点都取出来,大部分的时候,我们需要分多步来实现。

一下内容就不用代码展示了,后面在讲实战案例时,会用到,大家可以仔细了解。

  • 获取直接子节点:通过contents属性或children属性,可以获取当前节点的直接子节点。其中contents属性返回一个列表,children属性返回迭代器,是一个可迭代对象,该对象需要使用for 循环遍历。
  • 获取所有的子孙节点:需要使用descendants属性,该属性返回一个产生器,需要使用for循环进行迭代才可以输出产生器的值。
  • 选择父节点:需要使用parent属性,如果要选取某个节点的所有父节点,需要使用parents属性。
  • 选择兄弟节点:需要通过next_sibling属性获得当前节点的下一个兄弟节点,通过previous_sibling属性获得当前节点的上一个兄弟节点。

2、方法选择器

1、find_all方法

find_all方法用于根据节点名、属性、文本内容等选择所有符合要求的节点。

def find_all(self,name=None,attrs={},recursive=True,text=None,limit=None,**kwargs)
  • name参数:用于指定节点名,find_all方法会选取所有节点名与name参数值相同的节点。
  • attrs参数:attrs其实一般是用来限制name参数的,当name参数带有属性时,我们可以根据属性限制选取的节点。
  • text参数:通过text参数可以搜索匹配的文本节点

2、find方法

find方法与find_all方法有以下几点不同:

  • find方法用于查询满足条件的第一个节点
  • 两种方法的返回值不一样

3、CSS选择器

基本用法:常见的CSS选择器有如下几个

  • .classname:选取样式名为classname的节点,也就是class属性值是classname的节点(注意:必须在属性名前面带上“.”,这个是class的唯一标识)
  • idname:选取id属性值为idname的节点(注意:“#”是ID的唯一标识)

  • nodename:选取节点名为nodename的节点
实战案例:抓取酷狗网站TOP500的歌单(https://www.kugou.com/)
import requests
import random
import time
from bs4 import BeautifulSoup
from UA_info import ua_list
class KuGouSpider(object):
    def __init__(self):
        self.headers = {
            'User-Agent':random.choice(ua_list),
        }
    def get_html(self):
        #这个URL需要分析一下,其实也很好得到,稍微观察一下就会发现只有一个值在变
        for i in range(1,20):
            url = f'https://www.kugou.com/yy/rank/home/{i}-8888.html?from=rank'
            response = requests.get(url, headers=self.headers)
            #print(response.status_code)
            if response.status_code == 200:
                html = response.text
                self.bs4_html_parse(html)
    def bs4_html_parse(self,html):
        soup = BeautifulSoup(html,'lxml')
        #抓取歌曲的排名,根据CSS选择器爬取
        ranks = soup.select('span.pc_temp_num')
        #爬取歌手和歌曲名
        titles = soup.select('div.pc_temp_songlist>ul>li>a')
        #爬取歌曲时间
        times = soup.select('span.pc_temp_time')
        #存入字典打印
        for rank,title,time in zip(ranks,titles,times):
            data = {
                'rank':rank.get_text().strip(),
                'singer':title.get_text().split('-')[0].strip(),
                'song':title.get_text().split('-')[1].strip(),
                'time':time.get_text().strip()
            }
            print(data)

if __name__ == '__main__':
    kugou = KuGouSpider()
    kugou.get_html()
{'rank': '1', 'singer': '偷心', 'song': '张学友', 'time': '4:21'}
{'rank': '2', 'singer': '离别开出花 (弹唱版)', 'song': '就是南方凯', 'time': '2:52'}
{'rank': '3', 'singer': '你总要学会往前走', 'song': '任夏', 'time': '3:07'}
{'rank': '4', 'singer': '我还想她', 'song': '林俊杰', 'time': '4:07'}
{'rank': '5', 'singer': '我期待的不是雪 (而是有你的冬天)', 'song': '张妙格', 'time': '2:46'}
{'rank': '6', 'singer': '句号', 'song': 'G.E.M. 邓紫棋', 'time': '3:55'}
{'rank': '7', 'singer': '安和桥', 'song': '宇西', 'time': '4:08'}
{'rank': '8', 'singer': '是你 (Live)', 'song': '张靓颖、王赫野', 'time': '3:54'}
{'rank': '9', 'singer': '姑娘别哭泣 (弹唱版)', 'song': '柯柯柯啊', 'time': '2:57'}
{'rank': '10', 'singer': '我想念', 'song': '汪苏泷', 'time': '3:44'}
{'rank': '11', 'singer': '夏夜最后的烟火', 'song': '颜人中', 'time': '4:39'}
{'rank': '12', 'singer': '你总要学会往前走 (男版)', 'song': '小灿', 'time': '3:21'}
{'rank': '13', 'singer': 'Back Seat', 'song': 'JYJ', 'time': '3:10'}
{'rank': '14', 'singer': '有形的翅膀', 'song': '张韶涵', 'time': '3:36'}
{'rank': '15', 'singer': '西楼儿女', 'song': '岳云鹏', 'time': '4:20'}
{'rank': '16', 'singer': '瞬', 'song': '郑润泽', 'time': '4:44'}
{'rank': '17', 'singer': 'All My People', 'song': 'Alexandra Stan、Manilla Maniacs', 'time': '3:19'}

四、pyquery库

1、pyquery的基本用法

pyquery包中有一个PyQuery类,使用pyquery要先导入该类,然后创建该类的实例。可以通过三种方式将HTML文档传入该类的对象。

  • 字符串
  • URL
  • 文件

PyQuery对象有很多API可以操作HTML文档,最简单的是直接获取某个节点。

doc = pq(html)
for a in doc('a'):
    print(a.get('href'),a.text)
doc = pq(url='https://www.jd.com')
print(doc('title'))
#运行结果
http://www.baidu.com baidu.com
https://www.jd.com 京东商城
https://www.taobao.com 淘宝
https://www.microsoft.com 微软
https://www.google.com 谷歌
<title>京东(JD.COM)-正品低价、品质保障、配送及时、轻松购物!</title>

2、CSS选择器

from pyquery import PyQuery as pq
html = '''
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Beautiful Soup演示</title>
</head>
<body>
    <div id="panel">
        <ul class="list1">
            <li class="item1" value1="1234" value2="hello world">
                <a href="http://www.baidu.com">baidu.com</a>
            </li>
            <li class="item2">
                <a href="https://www.jd.com">京东商城</a>
            </li>
            <li class="item3">
                <a href="https://www.taobao.com">淘宝</a>
            </li>
        </ul>
        <ul class="list2">
            <li class="item4">
                <a href="https://www.microsoft.com">微软</a>
            </li>
            <li class="item5">
                <a href="https://www.google.com">谷歌</a>
            </li>
        </ul>
    </div>
</body>
</html>
'''
doc = pq(html)
#提取id属性值为panel,并且在该节点中所有属性值为list1的所有节点
result = doc('#panel .list1')
print(type(result))
#可以发现result任然是PyQuery对象
print(result)
print(result('.item1'))
print(result('a')[1].get('href'),result('a')[1].text)
#运行结果
<class 'pyquery.pyquery.PyQuery'>
<ul class="list1">
            <li class="item1" value1="1234" value2="hello world">
                <a href="http://www.baidu.com">baidu.com</a>
            </li>
            <li class="item2">
                <a href="https://www.jd.com">京东商城</a>
            </li>
            <li class="item3">
                <a href="https://www.taobao.com">淘宝</a>
            </li>
        </ul>
        
<li class="item1" value1="1234" value2="hello world">
                <a href="http://www.baidu.com">baidu.com</a>
            </li>
            
https://www.jd.com 京东商城

3、查找节点

  • 查找子节点:使用find方法,还可以使用children方法,该方法需将CSS选择器作为参数传入
  • 查找父节点:通过parent方法
  • 查找兄弟节点:使用sibling方法
  • 获取节点信息:获取节点信息分为5类信息
    • 节点名称:使用tag属性获取
    • 节点属性:使用get方法获取
    • 节点文本:使用text方法获取
    • 整个节点的HTML代码:使用etree的tostring函数
    • 节点内部的HTML代码:使用html方法

4、修改节点

由于修改节点方法太多,我在这里就只讲一些比较常用的方法。

①:添加和移除节点的样式

doc = pq(html)
li = doc('.item2')
li.add_class('myitem')
print(li)
li.remove_class('item2')
print(li)

②:修改节点属性和文本内容

使用attr方法可以向结点添加新的属性,如果添加的属性存在,则会修改原来的属性值。attr方法需要传入两个参数,第一个参数是属性名,第二个参数是属性值。

使用removeAttr方法可以删除节点属性。

使用text方法和html方法修改节点文本内容

doc = pq(html)
li = doc('.item2')
li.attr('class','list')
print(li)
li.remove_attr('class')
print(li)
li.text('我不是淘宝')
print(li)
#运行结果
<li class="list">
                <a href="https://www.jd.com">京东商城</a>
            </li>
            
<li>
                <a href="https://www.jd.com">京东商城</a>
            </li>
            
<li>我不是淘宝</li>

③:删除节点

使用remove方法可以删除节点,该方法接收一个字符串类型的参数,用于指定要删除的节点名。

5、伪类选择器

伪类选择器很强大,在这里告诉大家一个事情,就是在我们使用浏览器时,打开开发者环境,我们用右键点击想要获取的内容时,然后点击copy,然后在选择copy select就可以获取解析语法,一般比较复杂的解析语法就是使用伪类选择器的。

实战案例:抓取当当图书排行榜(https://www.dangdang.com/)
from requests import Session
from pyquery import PyQuery as pq
import random
from UA_info import ua_list
import time
session = Session()
class DangBookSpider(object):
    def __init__(self):
        self.headers = {
            'User-Agent':random.choice(ua_list),
            'Host':'search.dangdang.com',
            'Referer':'https://login.dangdang.com/',
        }
    def get_html(self):
        for i in range(1,10):
            url = f'https://search.dangdang.com/?key=python&act=input&sort_type=sort_sale_amt_desc&page_index={i}'
            response = session.get(url, headers=self.headers)
            if response.status_code == 200:
                html = response.text
                #在这里我使用了一个变量来接收生成器
                book_infos = self.pq_html_parse(html)
                #使用for循环迭代
                for book_info in book_infos:
                    print(book_info)
                    time.sleep(1)
    def pq_html_parse(self,html):
        doc = pq(html)
        """
        在这里不建议大家直接获得想要的数据
        因为我尝试过了,基本上都查找不到
        建议大家一步一步查找
        """
        ul = doc('.bigimg')
        li_list = ul('li')
        for li in li_list.items():
            #获取图书名称
            a = li('a:first-child')
            title = a[0].get('title')
            #抓取价格
            span = li('.search_now_price')
            price = span[0].text[1:]
            #获取作者名
            p = li('.search_book_author')
            author = p('a:first-child').attr('title')
            #可以起到一个挂起的作用,相当于return,但是比return更好用
            yield {
                'title':title,
                'price':price,
                'author':author
            }
if __name__ == '__main__':
    dang = DangBookSpider()
    dang.get_html()
{'title': ' Python编程三剑客第3版:Python编程从入门到实践第3版+快速上手第2版+极客项目编程(当当套装共3册)', 'price': '21.60', 'author': None}
{'title': ' 零基础学Python(Python3.10全彩版)编程入门 项目实践 同步视频', 'price': '23.10', 'author': None}
{'title': ' Python编程从入门到实践 第3版', 'price': '69.80', 'author': '[美]埃里克・马瑟斯(Eric Matthes)'}
{'title': ' 用Python编程和实践!数学教科书 机器学习 chatgpt聊天机器人 深度学习中必要的基础知识 人工智能 数学基础书', 'price': '34.90', 'author': None}
{'title': ' 大话Python机器学习实战手册 chatgpt聊天机器人 python深度学习人工智能丛书 机器学习方法 python', 'price': '34.90', 'author': None}
{'title': ' 一步到位!Python数据科学与人工智能应用实战(NumPy、Pandas、Matplotlib、Scikit-lear', 'price': '29.50', 'author': None}
{'title': ' 少儿编程', 'price': '34.90', 'author': None}
{'title': ' 矩阵力量:线性代数全彩图解+微课+Python编程', 'price': '129.00', 'author': '姜伟生'}
{'title': ' 零基础学Python(Python3.10全彩版)编程入门 项目实践 同步视频', 'price': '23.10', 'author': '明日科技(Mingri Soft)'}
{'title': ' AI时代系列丛书AI时代程序员+产品经理+项目经理+架构师+代Python量化交易实战', 'price': '43.50', 'author': None}
{'title': ' Python编程从零基础到项目实战PYTHON(微课视频版) python编程 从入门到实践 python学习手册 py', 'price': '28.40', 'author': None}
{'title': ' Excel机器学习', 'price': '29.90', 'author': '[美] 周红'}
posted on 2024-01-17 18:31  星辰与Python  阅读(103)  评论(0编辑  收藏  举报