爬虫入门三

数据解析

1. 正则表达式

正则表达式教程

正则表达式30分钟入门教程

pyre_ebb9ce1c-e5e8-4219-a8ae-7ee620d5f9f1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import re

one = 'abc 123'
patter = re.compile('\d+')
# match 从头匹配 匹配一次,无法匹配时将返回None
result = patter.match(one)

# search 从任意位置 , 匹配一次
result = patter.search(one)

# findall 查找符合正则的 内容 -- list
result = patter.findall(one)

# sub 替换字符串
result = patter.sub('#',one)

# split 拆分
patter = re.compile(' ')
result = patter.split(one)

2. XPath

XPath教程

XPath,全称 XML Path Language,即 XML 路径语言,它是一门在XML文档中查找信息的语言。XPath 最初设计是用来搜寻XML文档的,但是它同样适用于 HTML 文档的搜索。

XPath概览

XPath 的选择功能十分强大,它提供了非常简洁明了的路径选择表达式,另外它还提供了超过 100 个内建函数用于字符串、数值、时间的匹配以及节点、序列的处理等等,几乎所有我们想要定位的节点都可以用XPath来选择。

XPath 于 1999 年 11 月 16 日 成为 W3C 标准,它被设计为供 XSLT、XPointer 以及其他 XML 解析软件使用,更多的文档可以访问其官方网站:https://www.w3.org/TR/xpath/

XPath常用规则

nodename选取此节点的所有子节点

/从当前节点选取直接子节点

//从当前节点选取子孙节点

.选取当前节点

..选取当前节点的父节点

@选取属性

例如:

1
//title[@lang='eng']

这就是一个 XPath 规则,它就代表选择所有名称为 title,同时属性 lang 的值为 eng 的节点。

在后文我们会介绍 XPath 的详细用法,通过 Python 的 LXML 库利用 XPath 进行 HTML 的解析。

实例引入

我们现用一个实例来感受一下使用 XPath 来对网页进行解析的过程,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from lxml import etree
text = '''
<div>
<ul>
<li class="item-0"><a href="https://ask.hellobi.com/link1.html">first item</a></li>
<li class="item-1"><a href="https://ask.hellobi.com/link2.html">second item</a></li>
<li class="item-inactive"><a href="https://ask.hellobi.com/link3.html">third item</a></li>
<li class="item-1"><a href="https://ask.hellobi.com/link4.html">fourth item</a></li>
<li class="item-0"><a href="https://ask.hellobi.com/link5.html">fifth item</a>
</ul>
</div>
'''
html = etree.HTML(text)
result = etree.tostring(html)
print(result.decode('utf-8'))

另外我们也可以直接读取文本文件进行解析,示例如下:

1
2
3
4
5
from lxml import etree

html = etree.parse('./test.html', etree.HTMLParser())
result = etree.tostring(html)
print(result.decode('utf-8'))

所有节点

我们一般会用 // 开头的 XPath 规则来选取所有符合要求的节点,以上文的 HTML 文本为例,如果我们要选取所有节点,可以这样实现:

1
2
3
4
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//*')
print(result)

我们在这里使用 * 代表匹配所有节点,也就是整个 HTML 文本中的所有节点都会被获取,可以看到返回形式是一个列表,每个元素是 Element 类型,其后跟了节点的名称,如 html、body、div、ul、li、a 等等,所有的节点都包含在列表中了。

当然此处匹配也可以指定节点名称,如果我们想获取所有 li 节点,示例如下:

1
2
3
4
5
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li')
print(result)
print(result[0])

在这里我们要选取所有li 节点可以使用 //,然后直接加上节点的名称即可,调用时直接调用 xpath() 方法即可提取。

子节点

我们通过/// 即可查找元素的子节点或子孙节点,加入我们现在想选择li 节点所有直接 a子节点,可以这样来实现:

1
2
3
4
5
from lxml import etree

html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li/a')
print(result)

在这里我们通过追加一个 /a即选择了所有li节点的所有直接 a子节点,因为//li是选中所有li节点, /a是选中li节点的所有直接子节点a,二者组合在一起即获取了所有li节点的所有直接 a 子节点。

父节点

我们知道通过连续的///可以查找子节点或子孙节点,那假如我们知道了子节点怎样来查找父节点呢?在这里我们可以用..来获取父节点。

比如我们现在首先选中 href 是 link4.html 的 a 节点,然后再获取其父节点,然后再获取其 class 属性,代码如下:

1
2
3
4
5
from lxml import etree

html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//a[@href="https://ask.hellobi.com/link4.html"]/../@class')
print(result)

同时我们也可以通过 parent:: 来获取父节点,代码如下:

1
2
3
4
5
from lxml import etree

html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//a[@href="https://ask.hellobi.com/link4.html"]/parent::*/@class')
print(result)

属性匹配

在选取的时候我们还可以用 @ 符号进行属性过滤,比如在这里如果我们要选取 class 为 item-1 的 li 节点,可以这样实现:

1
2
3
4
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]')
print(result)

在这里我们通过加入 [@class=”item-0”] 就限制了节点的 class 属性为 item-0,而 HTML 文本中符合条件的 li 节点有两个,所以返回结果应该返回两个匹配到的元素,结果如下:

1
[<Element li at 0x10a399288>, <Element li at 0x10a3992c8>]

文本获取

我们用 XPath 中的 text() 方法可以获取节点中的文本,我们接下来尝试获取一下上文 li 节点中的文本,代码如下:

1
2
3
4
5
from lxml import etree

html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]/text()')
print(result)

运行结果如下:

1
['\n     ']

很奇怪的是我们并没有获取到任何文本,而是只获取到了一个换行符,这是为什么呢?因为 XPath 中 text() 前面是 /,而此 / 的含义是选取直接子节点,而此处很明显 li 的直接子节点都是 a 节点,文本都是在 a 节点内部的,所以这里匹配到的结果就是被修正的 li 节点内部的换行符,因为自动修正的li节点的尾标签换行了。

即选中的是这两个节点:

1
2
3
<li class="item-0"><a href="link1.html">first item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a>
</li>

其中一个节点因为自动修正,li 节点的尾标签添加的时候换行了,所以提取文本得到的唯一结果就是 li 节点的尾标签和 a 节点的尾标签之间的换行符。

因此,如果我们想获取 li 节点内部的文本就有两种方式,一种是选取到 a 节点再获取文本,另一种就是使用 //,我们来看下二者的区别是什么。

首先我们选取到 a 节点再获取文本,代码如下:

1
2
3
4
5
from lxml import etree

html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]/a/text()')
print(result)

运行结果:

1
['first item', 'fifth item']

可以看到这里返回值是两个,内容都是属性为 item-0 的 li 节点的文本,这也印证了我们上文中属性匹配的结果是正确的。

在这里我们是逐层选取的,先选取了 li 节点,又利用 / 选取了其直接子节点 a,然后再选取其文本,得到的结果恰好是符合我们预期的两个结果。

我们再来看下用另一种方式 // 选取的结果,代码如下:

1
2
3
4
5
from lxml import etree

html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]//text()')
print(result)

运行结果:

1
['first item', 'fifth item', '\n     ']

不出所料,这里返回结果是三个,可想而知这里是选取所有子孙节点的文本,其中前两个就是 li 的子节点 a 节点内部的文本,另外一个就是最后一个 li 节点内部的文本,即换行符。

所以说,如果我们要想获取子孙节点内部的所有文本,可以直接用 // 加 text() 的方式获取,这样可以保证获取到最全面的文本信息,但是可能会夹杂一些换行符等特殊字符。如果我们想获取某些特定子孙节点下的所有文本,可以先选取到特定的子孙节点,然后再调用 text() 方法获取其内部文本,这样可以保证获取的结果是整洁的。

属性获取

我们知道了用 text() 可以获取节点内部文本,那么节点属性该怎样获取呢?其实还是用 @ 符号就可以,例如我们想获取所有 li 节点下所有 a 节点的 href 属性,代码如下:

1
2
3
4
5
from lxml import etree

html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li/a/@href')
print(result)

在这里我们通过 @href 即可获取节点的 href 属性,注意此处和属性匹配的方法不同,属性匹配是中括号加属性名和值来限定某个属性,如 [@href=”https://ask.hellobi.com/link1.html“],而此处的 @href 指的是获取节点的某个属性,二者需要做好区分。

运行结果:

1
['link1.html', 'link2.html', 'link3.html', 'link4.html', 'link5.html']

可以看到我们成功获取了所有 li 节点下的 a 节点的 href 属性,以列表形式返回。

属性多值匹配

有时候某些节点的某个属性可能有多个值,例如下面例子:

1
2
3
4
5
6
7
from lxml import etree
text = '''
<li class="li li-first"><a href="https://ask.hellobi.com/link.html">first item</a></li>
'''
html = etree.HTML(text)
result = html.xpath('//li[@class="li"]/a/text()')
print(result)

在这里 HTML 文本中的 li 节点的 class 属性有两个值 li 和 li-first,但是此时如果我们还想用之前的属性匹配获取就无法匹配了,代码运行结果:

1
[]

这时如果属性有多个值就需要用 contains() 函数了,代码可以改写如下:

1
2
3
4
5
6
7
from lxml import etree
text = '''
<li class="li li-first"><a href="https://ask.hellobi.com/link.html">first item</a></li>
'''
html = etree.HTML(text)
result = html.xpath('//li[contains(@class, "li")]/a/text()')
print(result)

这样我们通过 contains() 方法,第一个参数传入属性名称,第二个参数传入属性值,这样只要此属性包含所传入的属性值就可以完成匹配了。

运行结果:

1
['first item']

此种选择方式在某个节点的某个属性有多个值的时候经常会用到,如某个节点的 class 属性通常有多个。

多属性匹配

另外我们可能还遇到一种情况,我们可能需要根据多个属性才能确定一个节点,这是就需要同时匹配多个属性才可以,那么这里可以使用运算符 and 来连接,示例如下:

1
2
3
4
5
6
7
from lxml import etree
text = '''
<li class="li li-first" name="item"><a href="https://ask.hellobi.com/link.html">first item</a></li>
'''
html = etree.HTML(text)
result = html.xpath('//li[contains(@class, "li") and @name="item"]/a/text()')
print(result)

在这里 HTML 文本的 li 节点又增加了一个属性 name,这时候我们需要同时根据 class 和 name 属性来选择,就可以 and 运算符连接两个条件,两个条件都被中括号包围,运行结果如下:

1
['first item']

这里的 and 其实是 XPath 中的运算符,另外还有很多运算符,如 or、mod 等等。

按序选择

有时候我们在选择的时候可能某些属性同时匹配了多个节点,但是我们只想要其中的某个节点,如第二个节点,或者最后一个节点,这时该怎么办呢?

这时可以利用中括号传入索引的方法获取特定次序的节点,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from lxml import etree

text = '''
<div>
<ul>
<li class="item-0"><a href="https://ask.hellobi.com/link1.html">first item</a></li>
<li class="item-1"><a href="https://ask.hellobi.com/link2.html">second item</a></li>
<li class="item-inactive"><a href="https://ask.hellobi.com/link3.html">third item</a></li>
<li class="item-1"><a href="https://ask.hellobi.com/link4.html">fourth item</a></li>
<li class="item-0"><a href="https://ask.hellobi.com/link5.html">fifth item</a>
</ul>
</div>
'''
html = etree.HTML(text)
result = html.xpath('//li[1]/a/text()')
print(result)
result = html.xpath('//li[last()]/a/text()')
print(result)
result = html.xpath('//li[position()<3]/a/text()')
print(result)
result = html.xpath('//li[last()-2]/a/text()')
print(result)

第一次选择我们选取了第一个 li 节点,中括号中传入数字1即可,注意这里和代码中不同,序号是以 1 开头的,不是 0 开头的。

第二次选择我们选取了最后一个 li 节点,中括号中传入 last() 即可,返回的便是最后一个 li 节点。

第三次选择我们选取了位置小于 3 的 li 节点,也就是位置序号为 1 和 2 的节点,得到的结果就是前 2 个 li 节点。

第四次选择我们选取了倒数第三个 li 节点,中括号中传入 last()-2即可,因为 last() 是最后一个,所以 last()-2 就是倒数第三个。

运行结果如下:

1
2
3
4
['first item']
['fifth item']
['first item', 'second item']
['third item']

在这里我们使用了 last()、position() 等函数,XPath 中提供了 100 多个函数,包括存取、数值、字符串、逻辑、节点、序列等处理功能,具体所有的函数作用可以参考:http://www.w3school.com.cn/xpath/xpath_functions.asp

节点轴选择

XPath 提供了很多节点轴选择方法,英文叫做 XPath Axes,包括获取子元素、兄弟元素、父元素、祖先元素等等,在一定情况下使用它可以方便地完成节点的选择,我们用一个实例来感受一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from lxml import etree

text = '''
<div>
<ul>
<li class="item-0"><a href="https://ask.hellobi.com/link1.html"><span>first item</span></a></li>
<li class="item-1"><a href="https://ask.hellobi.com/link2.html">second item</a></li>
<li class="item-inactive"><a href="https://ask.hellobi.com/link3.html">third item</a></li>
<li class="item-1"><a href="https://ask.hellobi.com/link4.html">fourth item</a></li>
<li class="item-0"><a href="https://ask.hellobi.com/link5.html">fifth item</a>
</ul>
</div>
'''
html = etree.HTML(text)
result = html.xpath('//li[1]/ancestor::*')
print(result)
result = html.xpath('//li[1]/ancestor::div')
print(result)
result = html.xpath('//li[1]/attribute::*')
print(result)
result = html.xpath('//li[1]/child::a[@href="https://ask.hellobi.com/link1.html"]')
print(result)
result = html.xpath('//li[1]/descendant::span')
print(result)
result = html.xpath('//li[1]/following::*[2]')
print(result)
result = html.xpath('//li[1]/following-sibling::*')
print(result)

运行结果:

1
2
3
4
5
6
7
[<Element html at 0x107941808>, <Element body at 0x1079418c8>, <Element div at 0x107941908>, <Element ul at 0x107941948>]
[<Element div at 0x107941908>]
['item-0']
[<Element a at 0x1079418c8>]
[<Element span at 0x107941948>]
[<Element a at 0x1079418c8>]
[<Element li at 0x107941948>, <Element li at 0x107941988>, <Element li at 0x1079419c8>, <Element li at 0x107941a08>]

第一次选择我们调用了 ancestor 轴,可以获取所有祖先节点,其后需要跟两个冒号,然后是节点的选择器,这里我们直接使用了 *,表示匹配所有节点,因此返回结果是第一个 li 节点的所有祖先节点,包括 html,body,div,ul。

第二次选择我们又加了限定条件,这次在冒号后面加了 div,这样得到的结果就只有 div 这个祖先节点了。

第三次选择我们调用了 attribute 轴,可以获取所有属性值,其后跟的选择器还是 *,这代表获取节点的所有属性,返回值就是 li 节点的所有属性值。

第四次选择我们调用了 child 轴,可以获取所有直接子节点,在这里我们又加了限定条件选取 href 属性为 link1.html 的 a 节点。

第五次选择我们调用了 descendant 轴,可以获取所有子孙节点,这里我们又加了限定条件获取 span 节点,所以返回的就是只包含 span 节点而没有 a 节点。

第六次选择我们调用了 following 轴,可以获取当前节点之后的所有节点,这里我们虽然使用的是 * 匹配,但又加了索引选择,所以只获取了第二个后续节点。

第七次选择我们调用了 following-sibling 轴,可以获取当前节点之后的所有同级节点,这里我们使用的是 * 匹配,所以获取了所有后续同级节点。

3. BeautifulSoup

BeautifulSoup文档

安装

1
2
3
pip install beautifulsoup4
# 安装解析器
pip install lxml

如何使用

1
2
3
4
5
6
# 将一段文档传入BeautifulSoup 的构造方法,就能得到一个文档的对象, 可以传入一段字符串或一个文件句柄.
from bs4 import BeautifulSoup

soup = BeautifulSoup(open("index.html"))

soup = BeautifulSoup("<html>data</html>")

对象种类

  • TagTag对象与XML或HTML原生文档中的tag相同。

    1
    2
    3
    4
    soup = BeautifulSoup('<b class="boldest">Extremely bold</b>')
    tag = soup.b
    type(tag)
    # <class 'bs4.element.Tag'>
  • NavigableString。字符串常被包含在tag内.Beautiful Soup用 NavigableString 类来包装tag中的字符串。

    1
    2
    3
    4
    tag.string
    # u'Extremely bold'
    type(tag.string)
    # <class 'bs4.element.NavigableString'>
  • BeautifulSoupBeautifulSoup 对象表示的是一个文档的全部内容.大部分时候,可以把它当作 Tag 对象,它支持 遍历文档树搜索文档树 中描述的大部分的方法.

  • CommentComment是文档的注释部分。

    1
    2
    3
    4
    5
    markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>"
    soup = BeautifulSoup(markup)
    comment = soup.b.string
    type(comment)
    # <class 'bs4.element.Comment'>

遍历文档树

find_all()

find_all( name , attrs , recursive , string , **kwargs )

find_all() 方法搜索当前tag的所有tag子节点,并判断是否符合过滤器的条件.这里有几个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
soup.find_all("title")
# [<title>The Dormouse's story</title>]

soup.find_all("p", "title")
# [<p class="title"><b>The Dormouse's story</b></p>]

soup.find_all("a")
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

soup.find_all(id="link2")
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]

import re
soup.find(string=re.compile("sisters"))
# u'Once upon a time there were three little sisters; and their names were\n'
name 参数

name 参数可以查找所有名字为 name 的tag,字符串对象会被自动忽略掉.

简单的用法如下:

1
2
soup.find_all("title")
# [<title>The Dormouse's story</title>]

重申: 搜索 name 参数的值可以使任一类型的 过滤器 ,字符窜,正则表达式,列表,方法或是 True .

keyword 参数

如果一个指定名字的参数不是搜索内置的参数名,搜索时会把该参数当作指定名字tag的属性来搜索,如果包含一个名字为 id 的参数,Beautiful Soup会搜索每个tag的”id”属性.

1
2
soup.find_all(id='link2')
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]

如果传入 href 参数,Beautiful Soup会搜索每个tag的”href”属性:

1
2
soup.find_all(href=re.compile("elsie"))
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]

搜索指定名字的属性时可以使用的参数值包括 字符串 , 正则表达式 , 列表, True .

下面的例子在文档树中查找所有包含 id 属性的tag,无论 id 的值是什么:

1
2
3
4
soup.find_all(id=True)
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

使用多个指定名字的参数可以同时过滤tag的多个属性:

1
2
soup.find_all(href=re.compile("elsie"), id='link1')
# [<a class="sister" href="http://example.com/elsie" id="link1">three</a>]

有些tag属性在搜索不能使用,比如HTML5中的 data-* 属性:

1
2
3
data_soup = BeautifulSoup('<div data-foo="value">foo!</div>')
data_soup.find_all(data-foo="value")
# SyntaxError: keyword can't be an expression

但是可以通过 find_all() 方法的 attrs 参数定义一个字典参数来搜索包含特殊属性的tag:

1
2
data_soup.find_all(attrs={"data-foo": "value"})
# [<div data-foo="value">foo!</div>]
find()

find( name , attrs , recursive , string , **kwargs )

find_all() 方法将返回文档中符合条件的所有tag,尽管有时候我们只想得到一个结果.比如文档中只有一个标签,那么使用 find_all() 方法来查找标签就不太合适, 使用 find_all 方法并设置 limit=1 参数不如直接使用 find() 方法.下面两行代码是等价的:

1
2
3
4
5
soup.find_all('title', limit=1)
# [<title>The Dormouse's story</title>]

soup.find('title')
# <title>The Dormouse's story</title>

唯一的区别是 find_all() 方法的返回结果是值包含一个元素的列表,而 find() 方法直接返回结果.

find_all() 方法没有找到目标是返回空列表, find() 方法找不到目标时,返回 None .

1
2
print(soup.find("nosuchtag"))
# None

soup.head.titletag的名字 方法的简写.这个简写的原理就是多次调用当前tag的 find() 方法:

1
2
3
4
5
soup.head.title
# <title>The Dormouse's story</title>

soup.find("head").find("title")
# <title>The Dormouse's story</title>
CSS选择器

Beautiful Soup支持大部分的CSS选择器 http://www.w3.org/TR/CSS2/selector.html [6] , 在 TagBeautifulSoup 对象的 .select() 方法中传入字符串参数, 即可使用CSS选择器的语法找到tag:

1
2
3
4
5
soup.select("title")
# [<title>The Dormouse's story</title>]

soup.select("p nth-of-type(3)")
# [<p class="story">...</p>]

通过tag标签逐层查找:

1
2
3
4
5
6
7
soup.select("body a")
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

soup.select("html head title")
# [<title>The Dormouse's story</title>]

找到某个tag标签下的直接子标签 [6] :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
soup.select("head > title")
# [<title>The Dormouse's story</title>]

soup.select("p > a")
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

soup.select("p > a:nth-of-type(2)")
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]

soup.select("p > #link1")
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]

soup.select("body > a")
# []

找到兄弟节点标签:

1
2
3
4
5
6
soup.select("#link1 ~ .sister")
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

soup.select("#link1 + .sister")
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]

通过CSS的类名查找:

1
2
3
4
5
6
7
8
9
soup.select(".sister")
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

soup.select("[class~=sister]")
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

通过tag的id查找:

1
2
3
4
5
soup.select("#link1")
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]

soup.select("a#link2")
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]

同时用多种CSS选择器查询元素:

1
2
3
soup.select("#link1,#link2")
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]

通过是否存在某个属性来查找:

1
2
3
4
soup.select('a[href]')
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

通过属性的值来查找:

1
2
3
4
5
6
7
8
9
10
11
12
13
soup.select('a[href="http://example.com/elsie"]')
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]

soup.select('a[href^="http://example.com/"]')
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

soup.select('a[href$="tillie"]')
# [<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

soup.select('a[href*=".com/el"]')
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]

通过语言设置来查找:

1
2
3
4
5
6
7
8
9
10
11
multilingual_markup = """
<p lang="en">Hello</p>
<p lang="en-us">Howdy, y'all</p>
<p lang="en-gb">Pip-pip, old fruit</p>
<p lang="fr">Bonjour mes amis</p>
"""
multilingual_soup = BeautifulSoup(multilingual_markup)
multilingual_soup.select('p[lang|=en]')
# [<p lang="en">Hello</p>,
# <p lang="en-us">Howdy, y'all</p>,
# <p lang="en-gb">Pip-pip, old fruit</p>]

返回查找到的元素的第一个

1
2
soup.select_one(".sister")
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>

对于熟悉CSS选择器语法的人来说这是个非常方便的方法.Beautiful Soup也支持CSS选择器API, 如果你仅仅需要CSS选择器的功能,那么直接使用 lxml 也可以, 而且速度更快,支持更多的CSS选择器语法,但Beautiful Soup整合了CSS选择器的语法和自身方便使用API.

爬虫解析数据实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
from ast import parse
import requests
import re
from bs4 import BeautifulSoup, BeautifulStoneSoup
from lxml import etree
from requests.exceptions import Timeout
import json

class BookSpider:
def __init__(self):
self.rootUrl = 'http://www.allitebooks.com/page/'
self.headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36'}

def getResponse(self, url):
try:
response = requests.get(url, headers=self.headers, timeout=200)
response.raise_for_status()
return response.content
except Exception as e:
print(e)

def parse_re_data(self, html):
patter1 = re.compile(r'<h2 class="entry-title"><a href="(.*?)" rel="bookmark">(.*?)</a></h2>', re.I)
patter2 = re.compile(r'<img width="\d*" height="\d*" src="(.*?)" class="attachment-post-thumbnail wp-post-image" alt="(.*?)" />')
results1 = patter1.findall(html.decode('utf-8'))
results2 = patter2.findall(html.decode('utf-8'))
book_list = []
for i, elem in enumerate(results1):
book_infos_dict = {}
book_infos_dict['name'] = elem[1]
book_infos_dict['url'] = elem[0]
book_infos_dict['pic'] = results2[i][0]
book_list.append(book_infos_dict)
return book_list

def parse_xpath_data(self, html):
x_data = etree.HTML(html)

names = x_data.xpath('//div[@class="entry-thumbnail hover-thumb"]//img/@alt')
book_urls = x_data.xpath('//div[@class="entry-thumbnail hover-thumb"]//a/@href')
pics = x_data.xpath('//div[@class="entry-thumbnail hover-thumb"]//img/@src')
books_list = []
for i, name in enumerate(names):
book_infos_dict = {}
book_infos_dict['name'] = name
book_infos_dict['url'] = book_urls[i]
book_infos_dict['pic'] = pics[i]
books_list.append(book_infos_dict)

return books_list

def parse_bs4_data(self, html):
soup = BeautifulSoup(html, 'lxml')
div_list = soup.find_all('div', attrs={"class": "entry-thumbnail hover-thumb"})
book_list = []
for div in div_list:
book_info_dict = {}
book_info_dict['name'] = div.img['alt']
book_info_dict['url'] = div.a['href']
book_info_dict['pic'] = div.img['src']
book_list.append(book_info_dict)
return book_list

def parse_selecter_data(self, data):

bs4_data = BeautifulSoup(data, 'lxml')
book_list = []
# 1.取出所有的书
article_list = bs4_data.select('article')



# 2.解析出 每本书的 信息
for book in article_list:
book_dict = {}
# 1.书名字
book_dict['book_name'] = book.select_one('.entry-title').get_text()

# # 2.书的图片url
book_dict['book_img_url'] = book.select_one('.attachment-post-thumbnail').get('src')
# 3. 书的url
book_dict['book_url'] = book.select_one('a[rel="bookmark"]').get('href')

book_list.append(book_dict)
return book_list

def save_data(self, data, path):
json_data = json.dumps(data)
with open(path, "w") as f:
f.write(json_data)
def run(self):
book_list = []
for i in range(1, 10):
url = self.rootUrl + str(i) + '/'
print(url)
html = self.getResponse(url)
# temp_list = self.parse_xpath_data(html)
# temp_list = self.parse_bs4_data(html)
# temp_lsit = self.parse_re_data(html)
temp_list = self.parse_selecter_data(html)
book_list += temp_list
self.save_data(book_list, 'books.json')

if __name__ == '__main__':
litterSpider = BookSpider()
litterSpider.run()