Python爬虫基础教程——lxml爬取入门

大家好,上次介绍了BeautifulSoup爬虫入门,本篇内容是介绍lxml模块相关教程,主要为Xpath与lxml.cssselect 的基本使用。

一、lxml介绍

引用官方的解释:

lxml XML工具箱是C库libxml2和libxslt的Python绑定 。它的独特之处在于它将这些库的速度和XML功能的完整性与本机Python API的简单性结合在一起,该Python API大多数都兼容,但优于著名的 ElementTree API。
lxml.etree是一个非常快速的XML库。这主要是由于libxml2的速度,例如解析器和序列化器,或XPath引擎。lxml的其他区域专门为在高层操作(例如树迭代器)中的高性能而编写。

简单的来说,lxml 是一种使用 Python 编写的库,可以迅速、灵活地处理 XML 和 HTML。

二、学习lxml库的目的

利用所学的XPath语法与lxml.cssselect模块,来快速定位特定元素以及节点信息,目的是提取HTML、XML目标数据

三、lxml安装

pip install lxml

pip install lxml

-i http://pypi.douban.com/simple/

--trusted-host http://pypi.douban.com

顺便说一句:我使用的开发工具还是vscode,不清楚的看一下之前的推文。

四、etree模块

使用etree模块,我们可以创建XML/Html元素及其子元素,我们用于操作Html或XML文件时非常有用。

4.1 Element类

用于ElementTree的API主容器对象。大多数XML树功能都是通过此类访问的。

下面尝试一下:

from lxml import etree

root=etree.Element('root')
print(root.tag)
child=etree.SubElement(root,'child') # 添加一个子节点
child.set('id','test_Id')
print(etree.tostring(root))          # tostring 为序列化

结果:

可以看出来我们可以使用Element类来创建xml内容。

4.2 tostring()

tostring()主要是对对象进行序列化,不能对集合进行序列化。

from lxml import etree

root = etree.XML('<root><a><b/></a></root>') 

print(etree.tostring(root))                        #default: method = 'xml'

print(etree.tostring(root, encoding='iso-8859-1')) #设置编码方式为 iso-8859-1

print(etree.tostring(root, pretty_print=True))     # 格式化

root = etree.XML('<html><head/><body><p>Hello<br/>Python知识学堂</p></body></html>')
etree.tostring(root, method='xml')                 # 默认值为:xml

etree.tostring(root, method='html')                #转成html

etree.tostring(root, method='text')                #转成文本即获取标签内的文本

不难理解,tostring()是序列化,那么fromstring()就是反序列化。

4.3 XML()/HTML()

类似于formstring()将字符串转成xmL/HTML对象

root = etree.XML("<root>data</root>")
etree.tostring(root)
root = etree.HTML("<p>data</p>")
etree.tostring(root)

关于etree模块就简单的介绍一些,有想深入了解的可以看一下官方文档。

五、XPath

lxml 支持XPath语法,XPath的语法还是比较简单的,我们尝试一下:

from lxml import etree

text="<div><h1>Python知识学堂</h1></div>"
html=etree.HTML(text)     #初始化生成一个XPath解析对象
result=html.xpath('//*')  #//代表获取子孙节点,*代表获取所有
for it in result:
    print(etree.tostring(it,encoding='utf-8').decode('utf-8'))

结果:

image
image.gif

下面介绍一下基本的语法以及举一些实列。

5.1 XPath常见语法

实例

from lxml import etree

def to_string(html):
    return etree.tostring(html,encoding='utf-8').decode('utf-8') #只能解析对象不能解析list

text='''
     <div class="item-1" style="color:red;"><a href="link1.html">Python知识学堂</a></div>
     <div class="item-2"><a href="link2.html"><div>学习Python</div></a></div>
     <div class="item-3 item-4"><a href="link5.html">很好玩</div>
'''
html=etree.HTML(text)              #初始化生成一个XPath解析对象  并且会补全html 比如:body标签
print(to_string(html))
# /表示从根节点开始选择
print(html.xpath('/div'))          #此时找不到div标签的 因为text 转成html 代码补全 导致初始的标签为html 
result=html.xpath('/html/body/div')#可以找到所有html下的body标签 再找到所有body标签下的div标签
for item in result:
    print(to_string(item))
print('------------------分隔符---------------')
# // 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置
result=html.xpath('/html//div')    #获取html 标签下的所有div 标签
for item in result:
    print(to_string(item))
print('------------------分隔符---------------')
# . 选取当前节点
result=html.xpath('./body')        #获取根节点下的body 标签
for item in result:
    print(to_string(item))
print('------------------分隔符---------------')
#..  选取当前节点的父节点  @选取属性
result=html.xpath('//a/../@class') #获取a标签的 父节点标签的 class的属性值
print(result) 
print('------------------分隔符---------------')
# * 匹配任何元素节点
result=html.xpath('//*')           #选取文档中的所有元素
for item in result:
    print(to_string(item))
print('------------------分隔符---------------')
# [@attribute='value'] 匹配属性值
result=html.xpath('//div[@class="item-2"]') #选取文档div标签 并且 含有class 属性且值为‘item-2’的所有元素
for item in result:
    print(to_string(item))

大家需要注意:

  1. xpath的返回结果都是集合(list)
  2. xpath只能作用与对象(object)上

六、lxml.cssselect

lxml.cssselect模块中最重要的是CSSSelector 即CSS选择器。了解前端知识的可能会容易一点。

6.1 安装

这个模块需要安装

pip install cssselect

6.2 CSS选择器

上面是一些常用的用法,可以参考一下。

我在网上看到关于使用cssselect的问题

<div class="aa bb"></div>
使用cssselect('.aa bb')是取不出来的,解决方法是将空格转换为 . 点。

可能不是很了解CSS选择器的规则,不知道class属性有多属性值时如何选择。

6.3 实例

from lxml.cssselect import CSSSelector
from lxml import etree

text='''
     <div class="item-1 item-2" style="color:red;"><a href="link1.html">Python知识学堂</a></div>
     <div class="item-2"><a href="link2.html"><div>学习Python</div></a></div>
     <div class="item-3 item-4"><a href="link5.html">很好玩</div>
'''
html=etree.HTML(text)      #初始化生成一个XPath解析对象  并且会补全html
result=html.cssselect('div.item-1.item-2 a') #选择div标签并且含有class属性且值含有"item-1"与"item-2" 下的a 标签子节点
for item in result:
    print(item.text)       # 获取标签的内容
    print(item.get('href'))#获取a 标签的属性值

从上面代码可以看出,如果需要通过多个class属性值来获取指定的标签,直接加.属性值。注意空格问题,.class1.class2与.class1 .class2的结果可能是不一样的,大家可以尝试一下。

与xpath一样,大家需要注意:

  1. cssselect 返回的结果都是集合(list)
  2. cssselect 只能作用与对象(object)上

七、案例

我们还是以之前的案例《获取省市区数据》,现在我们用lxml的方式重新实现一下

7.1 XPath方式

import requests
from lxml import etree
import time

class Demo():
    def __init__(self):
        base_url = 'http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2019/'
        trlist = self.get_data(base_url, "provincetable",'provincetr')#查看页面,就知道所有的省所在的tr上都有唯一的class='provincetr'
        for tr in trlist:
            td=tr.xpath('.//td')[0]
            a=td.find('a')
            if a is None:
                continue
            print("省:" + a.text)                    #获取每个省
            time.sleep(0.5)
            c_url=base_url+a.xpath('./@href')[0]     #获取下级城市地址
            c_trlist= self.get_data(c_url, "citytable","citytr")
            for c_tr in c_trlist:
                c_tds=c_tr.xpath('.//td')
                a=c_tds[0].find('a')
                t_url=base_url+a.xpath('./@href')[0] #获取下级区县地址
                print('市:('+a.text +')'+c_tds[1].find('a').text)
                time.sleep(0.5)

                t_list=self.get_data(t_url,"countytable","countytr")
                if len(t_list)==0:
                    t_list=self.get_data(t_url,"towntable","towntr")
                for t_tr in t_list:
                    t_tds=t_tr.xpath('.//td')
                    a=t_tds[0].find('a')
                    if a is None:
                        continue
                    print('区/县:('+a.text +')'+t_tds[1].find('a').text)

    def get_data(self, url, table_attr,attr):
        response = requests.get(url)
        response.encoding = 'gb2312'                 #编码转换
        tree=etree.HTML(response.text)               #初始化生成一个XPath解析对象
        trlist = tree.xpath('//table[@class="'+table_attr+'"]//tr[@class="'+attr+'"]')
        return trlist

if __name__ == '__main__':
    Demo()

7.2 lxml.cssselect方式

import requests
from lxml import etree
from lxml.cssselect import CSSSelector
import time

class Demo():

    def __init__(self):
        base_url = 'http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2019/'
        trlist = self.get_data(base_url, "provincetable",'provincetr')#查看页面,就知道所有的省所在的tr上都有唯一的class='provincetr'
        for tr in trlist:
            tds=tr.cssselect('td')
            for td in tds:
                a=td.cssselect('a')
                if len(a) ==0:
                    continue
                print("省:" + a[0].text)           #获取每个省
                time.sleep(0.5)
                c_url=base_url+a[0].attrib['href'] #获取下级城市地址
                c_trlist= self.get_data(c_url, "citytable","citytr")
                for c_tr in c_trlist:
                    c_tds=c_tr.cssselect('td')
                    c_a=c_tds[0].cssselect('a')
                    if len(c_a)==0:
                        continue
                    t_url=base_url+c_a[0].attrib['href'] #获取下级区县地址
                    print('市:('+c_a[0].text +')'+c_tds[1].cssselect('a')[0].text)
                    time.sleep(0.5)

                    t_list=self.get_data(t_url,"countytable","countytr")
                    if len(t_list)==0:
                        t_list=self.get_data(t_url,"towntable","towntr")
                    for t_tr in t_list:
                        t_tds=t_tr.cssselect('td')
                        t_a=t_tds[0].cssselect('a')
                        if len(t_a)==0:
                            continue
                        print('区/县:('+t_a[0].text +')'+t_tds[1].cssselect('a')[0].text)

    def get_data(self, url, table_attr,attr):
        response = requests.get(url)
        response.encoding = 'gb2312'              #编码转换
        tree=etree.HTML(response.text)            #初始化生成一个XPath解析对象
        trlist = tree.cssselect('table.'+table_attr+' tr.'+attr+'')
        return trlist

if __name__ == '__main__':
    Demo()

可以研究一下代码,可以用自己的方式实现出来。

八、总结

本篇文章主要讲述了关于lxml模块的一些内容,主要为XPath与lxml.cssselect的基本使用。lxml还有很多其他的功能,大家可以去官网上自行学习。

贴一下 官网地址:https://lxml.de/

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容