爬虫第一步:获取页面
一、信息在网络连接层的传递
(Bob 从Alice那里获得信息)
- Bob的电脑发送一个字节流,由信息组成,包含header和body。header包含一个本地路由器MAC地址的直接目的地,和Alice的IP地址的最终目的地。body包含他对接受Alice服务器应用程序的请求。
- Bob的本地路由器接收所有这些1和0,并将它们解释为一个包,来自Bob自己的MAC地址,目的地是Alice的IP地址。他的路由器把自己的IP地址作为“来自”IP地址印在包上,然后通过互联网发送出去。
- Bob的包穿过几个中间服务器,这些服务器将他的包指向Alice服务器上正确的物理/有线路径。
- Alice的服务器在她的IP地址接收数据包。
- Alice的服务器读取报头中的包端口目的地,并将其传递给适当的应用程序——web服务器应用程序。(对于web应用程序,包端口目的地几乎总是端口80;这可以看作是包数据的公寓号,而IP地址就像街道地址一样。)
- eb服务器应用程序从服务器处理器接收数据流。这些数据说明了如下内容:
- 这是一个GET请求。
- 请求以下文件:index.html。
- web服务器定位正确的HTML文件,将其打包成一个新的包发送给Bob,并将其发送到本地路由器,以便通过相同的过程将其传输回Bob的机器。
二、python 虚拟环境
如果您打算处理多个Python项目,或者您需要一种方法来轻松地将项目与所有关联的库捆绑在一起,或者您担心已安装的库之间可能存在冲突,那么您可以安装一个Python虚拟环境来保持所有内容分离并易于管理。
$ virtualenv scrapingEnv
创建一个环境scrapingEnv,产生了一个单独的文件夹,包含一套独立的python运行环境,安装的任何库或运行的脚本都将只在该虚拟环境下运行。运行虚拟环境:
$ cd scrapingEnv/
$ source bin/activate
(scrapingEnv)ryan$ pip install beautifulsoup4
(scrapingEnv)ryan$ python
> from bs4 import BeautifulSoup
>
(scrapingEnv)ryan$ deactivate
ryan$ python
> from bs4 import BeautifulSoup
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
三、urllib.request.urlopen 从一个网络地址获取html文件
from urllib.request import urlopen
html = urlopen('http://pythonscraping.com/pages/page1.html')
print(html.read())
urllib是一个标准的Python库(意味着您不需要安装任何额外的程序来运行这个示例),它包含用于跨web请求数据、处理cookie、甚至更改元数据(如标题和用户代理)的函数。
urlopen用于跨网络打开远程对象并读取它。它是一个相当通用的函数(它可以轻松地读取HTML文件、图像文件或任何其他文件流)
四、BeautifulSoup 处理html页面
BeautifulSoup 通过修复糟糕的HTML和向我们提供表示XML结构的易于遍历的Python对象来帮助格式化和组织混乱的web。
安装:
$sudo apt-get install python-bs4
or:
$pip install beautifulsoup4
运行:
from urllib.request import urlopen
from bs4 import BeautifulSoup
html = urlopen('http://www.pythonscraping.com/pages/page1.html')
bs = BeautifulSoup(html.read(), 'html.parser') # 无需.read()
print(bs.h1)
----------------------Output--------------------------
<h1>An Interesting Title</h1>
bs = BeautifulSoup(html.read(), 'html.parser')将html文件进行解析,成为一个BeautifulSoup 对象,具有良好的结构层次:
html → <html><head>...</head><body>...</body></html>
head → <head><title>A Useful Page<title></head>
title → <title>A Useful Page</title>
body → <body><h1>An Int...</h1><div>Loremip...</div></body>
h1 → <h1>An Interesting Title</h1>
div → <div>Lorem Ipsum dolor...</div>
然后可以访问不同层次结构:比如获得h1 tag:
bs.h1
;bs.html.h1
;bs.body.h1
;bs.html.body.h1
都可以
其中参数'html.parser'表示将其以html良好结构进行解析,无需安装直接使用。其他还有'lxml'和'html5lib'两者都是解析方式,不过都需要先下载安装pip install lxml
及pip install html5lib
.
lxml格式可以处理更糟糕的html代码,可以进行修复,而且比html.parser更快。缺点在于需要安装,依赖于第三方C库,可能出现易使用型和移植性问题。html5lib是一个非常宽容的解析器,它甚至可以主动更正损坏的HTML。它还依赖于外部依赖,并且比lxml和html.parser都要慢。
五、可靠地连接并处理异常
获取页面出错
- 服务器上找不到页面 HTMLError
- 找不到服务器 URLError
from urllib.request import urlopen
from urllib.error import HTTPError
from urllib.error import URLError
try:
html_1 = urlopen('http://www.pythonscraping.com/pages/pagenofound.html')
html_2 = urlopen('https://pythonscrapingthisurldoesnotexist.com')
except HTTPError as e:
print(e)
except URLError as e:
print('The server could not be found!')
else:
print('It Worked!')
tag获取出错
如果从服务器成功检索到页面,仍然存在页面上的内容与预期不完全一致的问题。每次在BeautifulSoup对象中访问标记时,明智的做法是添加一个检查来确保标记确实存在。如果您试图访问一个不存在的标记,BeautifulSoup将返回一个None对象。如果要继续访问深层tag,则出现None对象的AttributeError错误。
try:
badContent = bs.nonExistingTag.anotherTag
except AttributeError as e:
print('Tag was not found')
else:
if badContent == None:
print ('Tag was not found')
else:
print(badContent)
爬虫第二步:HTML 解析和信息提取
注意:一般不要去通过使用多行语句层层深入来找到所要的信息。可能因为页面的变化而出错
bs.find_all('table')[4].find_all('tr')[2].find('td').find_all('div')[1].find('a')
接下来的beautifulsoup对象的信息提取都建立在获取页面内容之后:
from urllib.request import urlopen
from bs4 import BeautifulSoup
html = urlopen('http://www.pythonscraping.com/pages/page1.html')
bs = BeautifulSoup(html.read(), 'html.parser')
一、通过属性匹配获得特定tag
nameList = bs.findAll('span', {'class':'green'})
for name in nameList:
print(name.get_text())
- bs.find_all(tagName, tagAttributes)来获取页面上所有标记的列表,而不仅仅是第一个标记。
- tagName.get_text(),以便将内容从标记中分离出来。
find() 和 find_all()
BeautifulSoup的find()和find_all()是您可能最常用的两个函数。有了它们,您可以很容易地过滤HTML页面,根据它们的各种属性找到所需标记的列表,或者单个标记。
find_all(tag, attributes, recursive, text, limit, keywords)
find(tag, attributes, recursive, text, keywords)
tag: 可以传递标记的字符串名称,甚至Python字符串标记名称列表
.find_all(['h1','h2','h3','h4','h5','h6'])
attributes:获取属性的Python字典,并匹配包含这些属性之一的标记
.find_all('span', {'class':{'green', 'red'}})
recursive:布尔值。您希望文档深入到什么程度?如果recursive被设置为True, find_all函数将查找与参数匹配的标记的子标记和子标记的子标记。如果它是假的,它将只查看文档中的顶级标记。默认情况下,find_all递归工作(recursive设置为True)
text:根据标记的文本内容进行匹配,而不是根据标记本身的属性进行匹配
nameList = bs.find_all(text='the prince')
print(len(nameList))
keywords:允许您选择包含特定属性或属性集的标记
title = bs.find_all(id='title', class_='text')
但keywords可以用attributes参数替代:
bs.find_all('', {'id':'text','class':'text'})
二、通过html组织结构获取tag
children and descendants 直接子代和所有后辈
在BeautifulSoup库和许多其他库中,在子库和后代库之间有一个区别:就像在人类族谱中一样,子库总是恰好比父库低一个标记,而后代库可以在父库下面的树中的任何级别。
一般来说,BeautifulSoup函数总是处理选中的当前标记的后代。例如,bs.body.h1
选择第一个h1标记,它是body标记的后代。它不会找到位于主体外部的标记。类似地,bs.div.find_all('img')
将在文档中找到第一个div标记,然后检索该div标记的后代的所有img标记的列表.
如果您希望只找到子代,可以使用.children标记
for child in bs.find('table',{'id':'giftList'}).children:
print(child)
所有后辈则使用.descendants
for all_child in bs.find('table',{'id':'giftList'}).descendants:
print(all_child)
siblings同层次兄弟姐妹
for sibling in bs.find('table',{'id':'giftList'}).tr.next_siblings:
print(sibling)
当你得到一个对象的兄弟siblings对象时,该对象本身将不会包含在列表中。正如函数名所暗示的,它只调用下面同层次的所有兄弟姐妹。例如,如果在列表中间选择一行,并对其调用next_siblings,则只返回后续的兄弟姐妹。因此,通过选择标题行并调用next_siblings,可以选择表中的所有行.
作为对next_siblings的补充,previous_siblings函数通常很有用,如果您希望得到的同胞标记列表的末尾有一个易于选择的标记。previous_siblings返回同层次的所有兄弟姐妹
也有next_sibling和previous_sibling函数,它们执行的函数几乎与next_sibling和previous_sibling相同,只不过它们返回的是单个标记,而不是它们的列表
parent 父代标记
bs.find('img',{'src':'../img/gifts/img1.jpg'}).parent.previous_sibling.get_text()
三、正则表达式
import re
images = bs.find_all('img',
{'src':re.compile('\.\.\/img\/gifts/img.*\.jpg')})
for image in images:
print(image['src'])
只打印以../img/gifts/img开头并且以.jpg结尾的相对图像路径。 输出结果如下:
../img/gifts/img1.jpg
../img/gifts/img2.jpg
../img/gifts/img3.jpg
../img/gifts/img4.jpg
../img/gifts/img6.jpg
四、获取属性
使用标记对象,Python属性列表可以通过调用以下命令自动访问:
myTag.attrs
这实际上返回了Python dictionary对象,可以使用以下行找到图像的源位置:
myTag.attrs['src']
五、Lambda 表达式
BeautifulSoup允许您将某些类型的函数作为参数传递到find_all函数中。
唯一的限制是这些函数必须以标记对象作为参数并返回布尔值。beautifulsoup遇到的每个标记对象都在这个函数中求值,值为True的标记将被返回,其余的将被丢弃。
bs.find_all(lambda tag: len(tag.attrs) == 2)
返回所有具有两个属性的标记
Lambda函数非常有用,您甚至可以使用它们来替换现有的BeautifulSoup函数
bs.find_all(lambda tag: tag.get_text() =='Or maybe he\'s only resting?')
等同于:
bs.find_all('', text='Or maybe he\'s only resting?')