1. 信息提取的一般方法
指从标记的信息中提取关注的内容。上一章提到的信息标记有三种形式:XML、JSON、YAML。
一般意义上的几种方法:
方法一:完整的解析信息的标记形式,再提取关键信息。像XML、JSON、YAML等,需要标记解析器,例如bs4库的标签树遍历,需要解析什么信息,去遍历这棵树就ok了。
优点:信息解析准确, 缺点:提取过程繁琐,速度慢。
方法二:无视任何标记信息,直接搜索关键信息。就像在一个Word文档中搜索关键词一样,根本不需要去关心文档具有什么样的标题形式和格式,只需要我们对信息的文本查找函数即可。
优点:提取过程简单,速度较快。 缺点:提取结果缺乏准确性。
二者方法哪种好呢?现实生活中我们使用的,是一种融合的方法。
融合方法:结合形式解析与搜索方法,来提取关键信息。
2. 一个小例子
例子的原网页:http://python123.io/ws/demo.html
实例:提取HTML中所有URL链接。
思路:(1)观察网页源代码,发现所有的URL链接都在< a>标签中。
(2)搜索到所有的< a>标签
(3)解析< a>标签格式,提取herf后的链接内容。
import requests
from bs4 import BeautifulSoup #BeautifulSoup是一个类
r=requests.get("http://python123.io/ws/demo.html")
r.encoding=r.apparent_encoding
demo=r.text
soup=BeautifulSoup(demo,"html.parser") #两个参数,第一个是要解析的文章,第二个是“html的解析器”
for link in soup.find_all('a'):
print(link.get('href')) #这里的find_all方法,待会会在后面讲。
3. 基于bs4库的HTML内容查找方法
上面讲到的<>.find_all(name,attrs,rescursive,string,**kwargs)
返回一个列表类型,存储查找的结果。各参数的说明如下:
- name:对标签名称的检索字符串
>>> soup.find_all('a')
[<a class="py1" href="http://www.icourse163.org/course/BIT-268001" id="link1">Basic Python</a>, <a class="py2" href="http://www.icourse163.org/course/BIT-1001870001" id="link2">Advanced Python</a>]
>>> soup.find_all(['a','b'])
[<b>The demo python introduces several python courses.</b>, <a class="py1" href="http://www.icourse163.org/course/BIT-268001" id="link1">Basic Python</a>, <a class="py2" href="http://www.icourse163.org/course/BIT-1001870001" id="link2">Advanced Python</a>]
>>> for tag in soup.find_all(True): #当参数为True时,默认是所有的标签
print(tag.name)
html
head
title
body
p
b
p
a
a
>>> for tag in soup.find_all(re.compile('b')): #打印以b开头的标签
print(tag.name)
body
b
- attrs:对标签属性值的检索字符串,可以标注属性检索
>>> soup.find_all('p','course') #对p标签的course属性值进行搜索
[<p class="course">Python is a wonderful general-purpose programming language. You can learn Python from novice to professional by tracking the following courses:
<a class="py1" href="http://www.icourse163.org/course/BIT-268001" id="link1">Basic Python</a> and <a class="py2" href="http://www.icourse163.org/course/BIT-1001870001" id="link2">Advanced Python</a>.</p>]
>>> soup.find_all(id='link1') #对所有标签的id=link1属性,进行搜索
[<a class="py1" href="http://www.icourse163.org/course/BIT-268001" id="link1">Basic Python</a>]
>>> soup.find_all(id='link')
[]
>>> soup.find_all(id=re.compile('link')) #所有以link开头的id
[<a class="py1" href="http://www.icourse163.org/course/BIT-268001" id="link1">Basic Python</a>, <a class="py2" href="http://www.icourse163.org/course/BIT-1001870001" id="link2">Advanced Python</a>]
- recursive:是否对子孙全部检索,默认为True
>>> soup.find_all('a')
[<a class="py1" href="http://www.icourse163.org/course/BIT-268001" id="link1">Basic Python</a>, <a class="py2" href="http://www.icourse163.org/course/BIT-1001870001" id="link2">Advanced Python</a>]
>>> soup.find_all('a',recursive=False)
[]
- string:<>…< />中字符串区域的检索字符串
>>> soup.find_all(string='Basic Python')
['Basic Python']
>>> soup.find_all(string=re.compile('python')) #搜索:字符串区域出现过‘python’字样的标签
['This is a python demo page', 'The demo python introduces several python courses.']
find_all()函数非常常用,所有有一种简写形式:
< tag>(…)等价于< tag>.find_all(…)
soup(…)等价于soup.find_all(…)
扩展方法:
方法 | 说明 |
---|---|
<>.find() | 搜索且返回一个结果,字符串类型。同.find_all()参数。 |
<>.find_parents() | 在先辈节点中搜索,返回列表类型。同.find_all()参数。 |
<>.find_parent() | 在先辈节点中返回一个结果,字符串类型。同.find_all()参数。 |
<>.find_next_sibling() | 在后续平行节点中返一个结果,字符串类型。同.find_all()参数。 |
<>.find_next_siblings() | 在后续平行节点中搜索,返回列表类型。同.find_all()参数。 |
<>.find_previous_siblings() | 在前续平行节点中搜索,返回列表类型。同.find_all()参数。 |
<>.find_previous_sibling() | 在前续平行节点中返一个结果,字符串类型。同.find_all()参数。 |
4. 实战:中国大学排名爬虫
技术路线:requests—bs4
定向爬虫,仅对输入URL进行爬取,不扩展爬取。
4.1 分析
爬取网页的链接:http://www.zuihaodaxue.cn/zuihaodaxuepaiming2016.html
先打开网页,查看源代码。
步骤一:确定可行性,确定我们想要的内容在html代码中可以找到。因为有一部分数据可能是通过JavaScripe等脚本语言生成。当你访问网页的时候,他的信息是动态生成的。在这种情况下,request库和bs4是无法提取的。爬取动态网页的方法以后会讲,这里先爬取静态网页。
步骤二:查看robots协议,发现它对爬虫没有限制。
步骤三:功能描述:打印出排名、大学名称、和总分。我们要在HTML文档中找到我们的目标的具体位置。
步骤四:对程序结构进行设计。
- 从网络上获取大学排名网页内容——getHTMLText()函数实现
- 提取网页内容中信息到合适的数据结构内(重点)——fillUnivList()函数实现
- 利用数据结构展示并输出结果——printUnivList()函数实现
4.2 实战
(1) 从网络上获取大学排名网页内容——getHTMLText()函数实现,这段代码比较简单,就不再具体分析了。
def getHTMLText(url):
try:
r=requests.get(url,timeout=30)
r.raise_for_status()
r.encoding=r.apparent_encoding
return r.text
except:
return ""
(2)这段代码,是提取网页内容中需要的信息,到合适的数据结构内——fillUnivList()函数实现
- 先要看懂HTML代码,知道我们的目标 “排名、大学名称、和总分”在哪个标签下面。然后找到这个标签。
- 看过之后,发现,我们需要的内容都在一个叫<tbody>的标签下面。
- 再对tbody这个标签具体分析,使用soup.tbody.prettify()打印出这个标签的标签树结构。分析得知,每个大学的信息都在tbody标签下面的tr标签里面。将tr标签打印出来如下(soup.tbody.tr.prettify())
<tr class="alt">
<td>
1
</td>
<td>
<div align="left">
清华大学
</div>
</td>
<td>
北京市
</td>
<td>
95.9
</td>
<td class="hidden-xs need-hidden indicator5">
100.0
</td>
<td class="hidden-xs need-hidden indicator6" style="display:none;">
97.90%
</td>
<td class="hidden-xs need-hidden indicator7" style="display:none;">
37342
</td>
<td class="hidden-xs need-hidden indicator8" style="display:none;">
1.298
</td>
<td class="hidden-xs need-hidden indicator9" style="display:none;">
1177
</td>
<td class="hidden-xs need-hidden indicator10" style="display:none;">
109
</td>
<td class="hidden-xs need-hidden indicator11" style="display:none;">
1137711
</td>
<td class="hidden-xs need-hidden indicator12" style="display:none;">
1187
</td>
<td class="hidden-xs need-hidden indicator13" style="display:none;">
593522
</td>
</tr>
- 这样就一目了然了,大学的排名是第一个td标签中的字符串,大学名字在第二个td标签中的字符串,总分在第三个td标签中的字符串。下面就是打印了。具体代码如下(一定要好好看注释!!!):
def fillUnivList(ulist,html):
soup=BeautifulSoup(html,"html.parser")
for tr in soup.find('tbody').children: #tr是tbody的儿子节点。 .children返回的是一个迭代类型
if isinstance(tr,bs4.element.Tag): #isinstance() 函数来判断一个对象是否是一个已知的类型,类似type()。 判断tr是否是一个标签类型
tds=tr("td") #等同于<tr>.find_all("td"),返回一个列表类型,列表中是一个tr标签下的所有td标签
ulist.append([tds[0].string,tds[1].string,tds[2].string])
(3)利用数据结构展示并输出结果——printUnivList()函数实现
def printUnivList(ulist,num): #\t相当于tab键
tplt="{0:^10}\t{1:{3}^10}\t{2:^10}" #{3}表示,当打印学校名字时,采用format函数第三个变量来填充,也就是使用中文空格来填充。
print(tplt.format("排名","学校","分数",chr(12288))) #中文对齐问题,采用中文字符填充
for i in range(num):
u=ulist[i]
print(tplt.format(u[0],u[1],u[2],chr(12288)))
这里需要用到Python中的format()函数,就不再讲解,具体的,可以自行百度。
选项 | 含义 |
---|---|
‘<’ | 强制字段在可用空间内左对齐(这是大多数对象的默认设置)。 |
‘>’ | 强制字段在可用空间内右对齐(这是数字的默认值)。 |
‘^’ | 强制字段在可用空间内居中。 |
4.3 完整代码
import requests
import bs4
from bs4 import BeautifulSoup #BeautifulSoup是一个类
def getHTMLText(url):
try:
r=requests.get(url,timeout=30)
r.raise_for_status()
r.encoding=r.apparent_encoding
return r.text
except:
return ""
def fillUnivList(ulist,html):
soup=BeautifulSoup(html,"html.parser")
for tr in soup.find('tbody').children: #tr是tbody的儿子节点,这是一个迭代类型
if isinstance(tr,bs4.element.Tag): #isinstance() 函数来判断一个对象是否是一个已知的类型,类似 type()。
tds=tr("td") #等同于tr.find_all(“td”),返回一个列表类型
ulist.append([tds[0].string,tds[1].string,tds[2].string])
def printUnivList(ulist,num): #\t相当于tab键
tplt="{0:^10}\t{1:{3}^10}\t{2:^10}"
print(tplt.format("排名","学校","分数",chr(12288))) #中文对齐问题
for i in range(num):
u=ulist[i]
print(tplt.format(u[0],u[1],u[2],chr(12288)))
def main():
uinfo=[]
url="http://www.zuihaodaxue.cn/zuihaodaxuepaiming2016.html"
html=getHTMLText(url)
fillUnivList(uinfo,html)
printUnivList(uinfo,20) #只打印前20所学校
main()
5. 总结
文章讲解了信息提取的一般方法。利用BeautifulSoup库和Requests库,一般来说信息提取的步骤大概可总结为:找到信息的具体位置(搜索),然后再用解析器进行局部遍历(形式解析),找到目标打印。
文章有些例子用到了Re正则表达式(但很少),正则表达式还是很重要的,在后面会接着详解。请多关注~!