python爬虫学习笔记之数据提取

参考博客:python爬虫学习笔记_fdk少东家的博客-CSDN博客

1、XPath语法和lxml库

1.01、什么是XPath?

xpath(XML Path Language)是一门在XMLHTML文档中查找信息的语言,可用来在XMLHTML文档中对元素和属性进行遍历。

1.02、XPath工具

  1. Chrome插件XPath Helper
  2. Firefox插件XPath Checker

1.03、XPath语法:

选取节点:

XPath使用路径表达式来选取XML文档中的节点或者节点集。这些路径表达式和我们在常规的电脑文件系统中看到的表达式非常相似。

表达式 描述 示例 结果
nodename 选取此节点的所有子节点 bookstore 选取bookstore下所有的子节点
/ 如果是在最前面,代表从根节点选取。否则选择某节点下的某个节点 /bookstore 选取根元素下所有的bookstore节点
// 从全局节点中选取节点,随便在哪个位置 //book 从全局节点中找到所有的book节点
@ 选取某个节点的属性 //book[@price] 选取所有book节点的price属性

谓语:

谓语用来查找某个特定节点或者包含某个指定的值的节点,被嵌套在方括号中。

在下面的表格中,我们列出了带有谓语的一些路径表达式,以及表达式的结果:

路径表达式 描述
/bookstore/book[1] 选取bookstore下的第一个子元素(下标是从1开始的。)
/bookstore/book[last()] 选取bookstore下的倒数第二个book元素
/bookstore/book[position()❤️] 选取bookstore下前面两个子元素
//book[@price] 选取拥有price属性的book元素
//book[@price=10] 选取所有属性price等于10的book元素
//book[contains(@class,‘fl’)] 当节点拥有多个class时,可使用模糊匹配

 通配符:

*表示通配符。

通配符 描述 示例 结果
* 匹配任意节点 /bookstore/* 选取bookstore下所有的
@* 匹配节点中的任何属性 //book[@*] 选取所有带有属性的book元素

选取多个路径:

通过在路径表达式中使用|运算符,可以选取若干个路径。

示例如下:

# 选取所有book元素以及book元素下所有的title元素
//bookstore/book | //book/title 

运算符:

运算符 描述 实例 返回值
| 计算两个节点集 //book | //cd 返回所有拥有book和cd元素的节点元素
+ 加法 6+4 10
- 减法 6-4 2
* 乘法 6*4 24
div 除法 8 div 4 2
= 等于 price=9.80 如果price=9.80,则返回true。否则返回false。
!= 不等于 price!=9.80 如果price不等于9.80,则返回true。否则返回false。
< 小于 price<9.80 如果真,则返回true,否则返回false
<= 小于或等于 price<=9.80 如果真,则返回true,否则返回false
> 大于 price>9.80 如果真,则返回true,否则返回false
>= 大于或等于 price>=9.80 如果真,则返回true,否则返回false
or price=9.80 or price=9.70 如果真,则返回true,否则返回false
and price=9.80 and price=9.70 如果真,则返回true,否则返回false
mod 计算除法的余数 5 mod 2 1

lxml是一个HTML/XML的解析器,主要的功能是如何解析和提取HTML/XML数据。

lxml和正则一样,也是用C实现的,是一款高性能的Python HTML/XML解析器,我们可以利用之前学习的XPath语法,来快速定位特定元素以及节点信息。

lxml python官方文档:lxml - Processing XML and HTML with Python

需要安装C语言库,可使用pip install lxml。
1.04、lxml的基本使用:

我们可以利用他来解析HTML代码,并且在解析HTML代码的时候,如果HTML代码不规范,他会自动的进行补全。示例代码如下:

从字符串中读取HTML代码:

# 使用 lxml的etree库
from lxml import etree

text = '''
你好 ''' html = etree.HTML(text) print(etree.tostring(html,encoding='utf-8').decode('utf-8'))

从文件中读取HTML代码:

假设存在一个hello.html文件。利用etree.parse方法,这个方法默认的解析器是XML解析,不会像etree.HTML类一样进行代码补充。示例代码如下:

from lxml import etree

# 有的网站的代码并不规范,直接parse解析会失败,这时需要修改解析器
parser = etree.HTMLParser(encoding='utf-8')

html = etree.parse('hello.html',parser=parser)
print(etree.tostring(html,encoding='utf-8').decode('utf-8'))

print(result)

注意: etree.parse默认的是XML的解析器,有的不规则的网页会解析失败,这时修改parse的解析器。parser = etree.HTMLParser(encoding='utf-8'),然后把parser传入etree.parse()即可。

1.05、在lxml中使用XPath语法:

1.获取所有li标签:

from lxml import etree

html = etree.parse('hello.html')
result = html.xpath('//li')

# 打印
  • 标签的元素集合 print(result)
  • 2.获取所有li元素下的所有class属性的值:

    from lxml import etree
    
    html = etree.parse('hello.html')
    result = html.xpath('//li/@class')
    
    print(result)
    

    3.获取li标签下href为www.baidu.com的a标签:

    from lxml import etree
    
    html = etree.parse('hello.html')
    result = html.xpath('//li/a[@href="www.baidu.com"]')
    
    print(result)
    

    4.获取li标签下href为www.baidu.com的a标签的文本信息,使用text()获取文本信息:

    from lxml import etree
    
    html = etree.parse('hello.html')
    result = html.xpath('//li/a[@href="www.baidu.com"]/text()')
    
    print(result)
    

    注意事项:

    1.使用xpath语法,应该使用Element.xpath方法。来执行xpath的选择。

    result = html.xpath('//li')
    

    xpath返回的永远是一个列表。

    2.获取文本,是通过xpath中的text()函数。示例代码如下:

    html.xpath('//li/a[1]/text()')
    

    3.在某个标签下,再执行xpath函数,获取这个标 签下的子孙元素,那么应该在斜杠之前加一个.,代表是在当前元素下获取。

    address = tr.xpath('./td[4]/text()')
    

    2、BeautifulSoup4

    和lxml一样,BeautifulSoup也是一个HTML/XML的解析器,主要的功能也是如何解析和提取HTML/XML数据。

    lxml指挥局部遍历,而BeautifulSoup是基于HTML DOM的,会载入整个文档,解析整个DOM树,因此时间和内存开销都会大很多,所以性能低于lxml

    BeautifulSoup用来解析HTML比较简单,api非常人性化,支持CSS选择器、python标准库中的HTML解析器,也支持lxmlXML解析器。

    2.01、安装和文档:

    1. 安装:pip install bs4
    2. 中文文档:Beautiful Soup 4.4.0 文档 — Beautiful Soup 4.2.0 中文 文档

    2.02、几大解析工具对比:

    解析工具 解析速度 使用难度
    BeautifulSoup 最慢 最简单
    lxml 简单
    正则 最快 最难

    2.03、四个常用的对象:

    Beautiful Soup将复杂HTML文档转换成一个复杂的树形结构,每个节点都是python对象,所有对象都可以归纳为4种:

    1. TagBeautifulSou中所有的标签都是Tag类型,并且BeautifulSou的对象其实本质上也是一个Tag类型。所以其实一些方法比如:findfind_all并不是BeautifulSou的,而是Tag的。
    2. NavigatableString继承自python中的str,用起来和str一样。
    3. BeautifulSouP继承自Tag。用来生成BeautifulSou树的。对于一些查找方法,比如:findselect这些,其实还是Tag的。
    4. Comment继承自NavigableString

     2.03-1、Tag

    通俗点讲就是HTML中的一个个的标签

    soup = BeautifulSoup(html,'lxml')
    table = soup.find('table')
    print(type(table))
    

    我们可以利用soup加标签名轻松的获取这些标签的内容,这些对象的类型是bs4.element.Tag。但是注意,他查找的是在所有内容中的第一个符合条件的要求的标签。如果要查询所有的标签,后面会进行介绍。

    Tag有两个重要的属性,分别为nameattrs

    print(soup.name)
    # [document] # soup对象本身比较特殊,他的name即为[document]
    
    print(soup.head.name)
    # head #对于其他内部标签,输出的值为标签本身的名称。
    
    print(soup.p.attrs)
    #{'class':['title'],'name':'dromouse'}
    #在这里,我们把p标签的所有属性打印输出,得到的类型是个字典
    
    print(soup.p['class']) #soup.p.get('class)
    # ['title'] #还可以利用get方法,传入属性的名称,二者等价。
    
    soup.p['class'] = "newClass"
    print(soup.p) #还可以对这些属性和内容进行修改
    

    2.03-2、NavigableString:

    如果拿到标签后,还想获取标签中的内容。那么可以通过tag.string获取标签中的文字。

    print(soup.p.string)
    # The Document's story
    
    print(type(soup.p.string))
    # thon
    

    2.03-3、BeautifulSoup:

    BeautifulSoup对象表示的是一个文档的全部内容,大部分时候,可以把它当作Tag对象,它支持遍历文档树和搜索文档树中描述的大部分方法。

    因为BeautifulSoup对象并不是真正的HTMLXMLTag,所以它没有nameattribute属性,但有时查看它的name属性是很方便的。所以BeautifulSoup对象包含了一个值为'[document]'的特殊属性.name

    soup.name
    # '[document]'
    

    2.03-4、Comment:

    TagNavigableStringBeautifulSoup几乎覆盖了htmlxml中所有的内容,但是还有一些特殊对象,容易让人担心内容是文档的注释部分:

    markup = ""
    soup = BeautifulSoup(markup)
    comment = soup.b.string
    print(type(comment))
    # 
    

    Comment对象是一个特殊类型的NavigableString对象。

    2.04、搜索文档树

    2.04-1、find和find_all方法:

    搜索文档书,一般用的比较多的方法就是两个方法,一个是find,一个是find_all。find方法是找到第一个满足条件的标签后立即返回,只返回一个元素。find_all方法是把所以满足条件的表签都返回。

    实例演示:

    1.获取所有tr标签

    trs = soup.find_all('tr')
    for tr in trs:
        print(tr)
    

    2.获取第二个tr标签

    tr = soup.find_all('tr',limit=2)[1]
    

    3.获取所有class等于even的tr标签

    trs = soup.find_all('tr',attrs={'class':'even'})
    for tr in trs:
        print(tr)
    

    4.将所有id等于test,class也等于test的a标签提取出来。

    aList = soup.find_all('a',id='test',class='test')
    # 也可以使用attrs属性选择
    for a in aList:
        print(a)
    

    5.获取所有a标签的href属性

    aList = soup.find_all('a')
    for a in aList:
        # 1.通过下标操作的方式
        print(a['href'])
        # 2.通过attrs属性的方式
        print(a.attrs['href'])
    

    6.获取所有职位信息(纯文本)

    • 1.string提取单个文本信息,
    • 2.strings提取标签下所有文本信息,
    • 3.stripped_strings提取标签下所有非空的文本信息。
    trs = soup.find_all('tr')[1:]
    zhiwei = {}
    for tr in trs:
        # tds = tr.find_all('td')
        # title = tds[0].string
        # city = tds[1].string
        # zhiwei['title'] = title
        # zhiwei['city'] = city
        infos = list(tr.stripped_strings)
        zhiwei['title'] = infos[0]
        zhiwei['city'] = infos[1]
        print(zhiwei)
    

    2.04-2、select方法:

    使用以上方法可以方便的找出元素,但有时候使用css选择器的方法可以更加方便。使用css选择器的语法,应该使用select方法。一下列出几种常用的css选择器方法:

    1.通过标签名查找:

    print(soup.select('a'))
    

    2.通过类名查找:

    通过类名查找,则应该在类名前面加一个.。比如查找class=sister的标签。

    tg = soup.select('.sister')
    

    3.通过id查找:

    通过id查找,一个在id名字前面加#号。

    print(soup.select('#idname'))
    

     4.组合查找:

    组合查找即和写class文件时,标签名与类名、id名进行组合原理一样,例如查找p标签中,id等于link1的内容,二者需要用空格分开:

    print(soup.select('p #link1'))
    

    直接子标签查找,这使用>分隔:

    print(soup.select('head>title'))
    

    5.通过属性查找:

    查找时还可以加入属性元素,属性需要用中括号括起来,注意属性和标签属于同一节点,所以中间不能加空格,否则会无法匹配到。

    print(soup.select('a[href="http://example.com/elsie"]'))
    

    6.获取内容

    以上select方法返回的结果都是列表,可以遍历的形式输出,然后用get_text()方法获取内容。

    soup = BeautifulSoup(html,'lxml')
    print(type(soup.select('title')))
    print(soup.select('title')[0].get_text())
    
    for title in soup.select('title'):
        print(title.get_text())
    

    2.04-3、find_all的使用:

    1. 在提取多个标签的时候,第一个参数是标签的名字。然后图个在提取标签的时候想要使用标签属性进行过滤,你们可以在这个方法中通过关键字参数的形式,将属性名以及对应的值传进去。或者是使用attrs属性,将所有的属性以及对于的值房子啊一个字典中传给attrs属性。
    2. 有时候,在提取标签的时候,不想提取那么多,那么可以使用limit参数。进行限制。

    2.04-4、find和find_all的区别:

    1. find:只返回满足条件的第一个标签
    2. find_all:返回满足条件的所有标签。是个列表。

    2.04-5、find和find_all的过滤条件:

    1. 关键字参数:将属性的名字作为关键字参数的名字,以及属性的值作为关键字参数的值进行过滤。
    2. attrs参数:将属性条件放到一个字典中,传给attrs参数。

    2.04-6、获取标签的属性: 

    # 1.通过下标操作的方式
    print(a['href'])
    # 2.通过attrs属性的方式
    print(a.attrs['href'])
    

    2.04-7、string、strings、stripped_strings属性和get_text方法:

    1. string:获取某个标签下的非标签字符串。返回的是个字符串。如果这个标签下有多行字符,那么就获取不到了。
    2. strings:获取某个标签下的子孙非标签字符串。返回的是个生成器
    3. stripped_strings:获取某个标签下的子孙非标签字符串,去掉空白字符。返回的是个生成器。
    4. get_text:获取某个标签下的子孙非标签字符串,不是以列表的形式返回。只是普通字符串。

     2.05、遍历文档树

    2.05-1、contents和children:

    返回某个标签下的直接子元素。其中也包括字符串。他们的区别是:contents返回的是一个列表,children返回的是一个迭代器。

    soup = BeautifulSoup(html,'lxml')
    
    head_tag = soup.head
    # 返回所有的子节点的列表
    print(head_tag.contents)
    
    # 返回所有的子节点的迭代器
    for child in head_tag.children:
        print(child)
    

    2.05-2、strings和stripped_strings

    如果tag中包含多个字符串,可以使用.strings来循环获取:

    for string in soup.strings:
        print(string)
    

    3、正则表达式和re模块

    什么是正则表达式(通俗理解):按照一定的规则,从某个字符串中匹配出想要的数据。这个规则就是正则表达式。

    ###3.01、正则表达式符号:

    1.[]

    • 1.用来指定字符集:[asdfghjkl],[a-zA-Z]
    • 2.元字符在字符集中不起作用:[akn$]
    • 3.不急匹配不在区间范围内的字符:[ ^io],匹配除io之外的任意字符。

    2.^

    • 1.匹配行首。除非设置MULTILINE标志,它只是匹配字符串的开始。在MULTILINE模式里,它也可以直接匹配字符串中的每个换行。

    3.$

    • 1.匹配行尾,行尾被定义为要么是字符串尾,要么是一个换行字符后面的任何位置。

     4.\

    • 1.反斜杠后面可以加不同的字符以表示不同的特殊意义。

    • 也可以用于取消所有的元字符,即当转义字符。

    符号 含义
    . 匹配除’\n’之外的任何单个字符,如果要匹配包括’\n’在内的任何字符,使用‘[.\n]’
    \d 匹配任何十进制数,相当于类[0-9]
    \D 匹配任何非数字字符;相当于类[ ^0-9]
    \s 匹配任何空白字符,相当于类[\t\n\r\f\v]
    \S 匹配任何非空白字符,相当于类[ ^\t\n\r\f\v]
    \w 匹配任何字母数字字符,相当于类[a-zA-Z0-9_]
    \W 匹配任何非字母数字字符,相当于类[ ^a-zA-Z0-9_]

    5. 重复:

    • 1.正则表达式第一个功能是能够匹配不定长的字符集,另一个功能就是可以指定正则表达式的一部分的重复次数。在{ }捏填写重复次数

    6.*

    • 1.指定前一个字符可以别匹配零次或更多次,而不是只有一次。匹配引擎会试着重复尽可能多的次数(不超过整数界定范围,20亿)
    • 2.a[bcd]*b----‘abcbd’。

     7.+

    • 1.表示匹配一次或更多次。
    • 2.和*的区别,*匹配零次或更多次,所有可以根本就不出现,而’+’则要求至少出现一次

     8.?

    • 1.加在重复的后面,做最小匹配,即非贪婪模式。
    • 2.匹配一次或零次,你可以认为它用于标识某事物是可选的

     9.{m,n}

    • 1.其中m和n是十进制整数,该限定符的意思是至少有m个重复,至多到n个重复。
    • 2.忽略m会认为下边界是0,而忽略n的结果将是上边界无穷大(实际是20亿)
    • 3.{0,}等同于‘’,{1,}等同于‘+’,而{0,1}则与‘?’相同。如果可以的话,最好使用‘’,‘+’或‘?’。

     10.()分组:

    • 1.利用()来进行多种正则的选择。匹配时优先返回分组的值。

     11.|

    • 1.匹配多个表达式或字符串。

     3.02、正则表达式常用的匹配规则:

    1.匹配某个字符串:

    text = 'hello'
    ret = re.march('he',text)
    print(ret.group())
    >>he
    

    以上便可以在hello中,匹配出he

    3.03、转义字符和原生字符串

    在正则表达式中,有些字符串是有特殊意义的字符。因此如果想要匹配这些字符,那么就必须使用反斜杠进行转义。比如$代表的是以…结尾,如果想要匹配$,那么就必须使用\$

    text = 'apple price is \$99,orange is $88'
    ret = re.search('\$(\d+)',text)
    print(ret.group())
    >>$99
    

    原生字符串:

    在正则表达式中,\是专门用来转义的。在python\也是用来转义的。因此如果想要在普通字符串中匹配出\,那么要给出4个\

    text = 'apple \c'
    ret = re.search('\\\\c',text)
    print(ret.group())
    

    因此要使用原生字符串就可以解决这个问题:

    text = 'apple \c'
    ret = re.search(r'\\c',text)
    print(ret.group())
    

    3.04、正则表达式函数:

    3.04-1、re.compile()函数

    compile函数用于编译正则表达式,生成一个正则表达式( Pattern )对象,供 match() 和 search() 这两个函数使用。

    语法格式为:re.compile(pattern[, flags])

    • pattern:一个字符串形式的正则,字符串前加r,反斜杠就不会被当作转义字符。

    • flags:可选,表示匹配模式,比如忽略大小写,多行模式等,具体参数如下:

    符号 含义
    re.I 忽略大小写
    re.L 表示特殊字符集\w, \W, \b, \B, \s, \S 依赖于当前环境
    re.M 多行模式
    re.S 即为.并且包括换行符在内的任意字符(单纯的.不包括换行符)
    re.U 表示特殊字符集 \w, \W, \b, \B, \d, \D, \s, \S 依赖于 Unicode 字符属性数据库
    re.X 为了增加可读性,忽略空格和 # 后面的注释

     3.04-2、用于匹配的函数:

    方法/属性 作用
    match() 在字符串刚开始的位置匹配,如果想要匹配换行符,传入一个flags=re.DOTALL就可以了。
    search() 扫描字符串,找到这个RE匹配的位置
    findall() 找到RE匹配的所有子串,并把它们作为一个列表返回
    finditer() 找到RE匹配的所有子串,并把它们作为一个迭代器返回

    如果没有匹配到,match()和search()将返回None,如果成功,返回一个‘MatchObject’实例 。

    MatchObject实例方法(即匹配成功后使用的方法):

    方法/属性 作用
    group() 返回被RE匹配的字符串。
    start() 返回匹配开始的位置。
    end() 返回匹配结束的位置
    span() 返回一个元组包含匹配(开始,结束)

    3.04-3、分组:

    在正则表达式中,可以对过滤到的字符串进行分组。分组使用()

    1. group:和group(0)是等价的,返回的是整个满足条件的字符串。
    2. groups:返回的是里面的子组。索引从1开始。
    3. group(1):返回的是第一个子组,可以传入多个。

    实例如下:

    text = 'apple price is $99,orange price is $10'
    ret = re.search(r'.*(\$\d+).*(\$\d+)',text)
    print(ret.group())
    print(ret.group(0))
    print(ret.group(1))
    print(ret.group(2))
    print(ret.groups())
    

    3.04-4、match():

    从开始的位置进行匹配。如果开始的位置没有匹配到,直接报错。

    text = 'hello'
    ret = re.match('h',text)
    print(ret.group())
    >>h
    

    如果第一个字母不是h,那么就会失败。

    text = 'ahello'
    ret = re.match('h',text)
    print(ret.group())
    >>AttributeError:'NoneType' object has no attribute 'group'
    

    如果想要匹配换行的数据,那么就要传入一个flag=re.DOTALL,就可以匹配换行符了。

    text = 'abc\nabc'
    ret = re.match('abc.+abc',text,re.DOTALL)
    print(ret.group())
    

    3.04-5、search():

    在字符串中找满足条件的字符。如果找到了,就返回。只会返回第一个满足条件的。

    text = 'apple price is $99,orange price is $88'
    ret = re.search('\d+',text)
    print(ret.group())
    >>99
    

    3.04-6、findall():

    找出满足条件的,返回的是一个列表。

    text = 'apple price is $99,orange price is $88'
    ret = re.search('\d+',text)
    print(ret.group())
    >>['99','88']
    

    3.04-7、检索替换re.sub():

    re模块提供了re.sub()用于替换字符串中的匹配项。

    语法:re.sub(pattern,repl,string,count=0,flags=0)

    参数:

    • pattern:正则中的模式字符串。
    • repl:替换的字符串,也可为一个函数。
    • string:要被查找替换的原始字符串。
    • count:模式匹配后替换的最大次数,默认为0,表示替换所有的匹配。

    3.04-8、检索替换re.subn()

    返回结果比re.sub()多了一个替换次数。

    3.04-9、re.split()

    split 方法按照能够匹配的子串将字符串分割后返回列表,它的使用形式如下:

    re.split(pattern, string[, maxsplit=0, flags=0])

    • pattern:匹配的正则表达式

    • string:要匹配的字符串

    • maxsplit:分割次数,maxsplit=1分割一次,默认为0,不限次数。

    import re
    s = '123+456-789*111'
    re.split(r'[\+\-\*]',s)
    >['123','456','789','111']
    

    你可能感兴趣的:(笔记,1024程序员节,python,爬虫)