用Python实现网络爬虫
这是《Web Scraping with Python》一书的阅读笔记。该笔记跳过了一些不必要的描述,对书的代码也做了核实,也引入了一些我自己对爬虫脚本实现的理解。
第一章 你的第一个网络爬虫程序
为了帮助理解,作者用了一个例子。假设Alice有一个网络服务器。而Bob想用一个台式机通过浏览器访问Alice的服务器上运行的某个网站。整个访问过程归纳如下:
1. Bob输入访问网站的地址后,Bob的电脑传输一段二进制的数据,这些数据包含数据头和数据内容。数据头包含发送方的mac地址和目的地的ip地址,而数据内容包含了针对Alice网络服务器的请求,例如,获得某个网页页面。
2. Bob的本地网络路由器将数据打包传输到Alice的ip地址。
3. Bob的数据最后通过物理电缆进行传输。
4. Alice的服务器接受到了Bob的数据包。
5. Alice的服务器识别存于数据头的端口号,发现是80,意味着这是一个网页请求,于是调用网页服务器相关的程序。
6. 网页服务器程序接受到如下信息::
- This is a GET request
- The following file is requested: index.html
7. 网页服务器程序载入正确的HTML 文件,并打包通过本地路由发送给Bob的电脑.
而Python的库包含了模拟浏览器访问某个页面的功能,如下:
from urllib.request import urlopen
html = urlopen("http://pythonscraping.com/pages/page1.html")
print(html.read())
这是一段Python3的程序,请用Python3.X的版本运行它。执行后,该网页的HTML内容会被打印出来,其实就是Chrome浏览器右键查看网页源代码可以看到的网页内容。
urllib 还是 urllib2?
Python2中用urllib2,而Python3中用urllib。urllib是Python的标准库,用于网页数据请求,处理Cookies,甚至更改请求者的数据头信息。因为正本书的代码都会涉及urllib的使用,所以有空的时候可以看看Python的官方文档:https://docs.python.org/3/library/urllib.html
BeautifulSoup的介绍和安装
一句话来概括,BeautifulSoup将不可能变成了可能,它将HTML的内容组织成了Python可以识别的对象格式。因为BeautifulSoup不是Python默认的库,我们要先安装它。本书用BeautifulSoup的第四个版本。下载地址:https://pypi.python.org/pypi/beautifulsoup4。可下载安装包:beautifulsoup4-4.5.3.tar.gz(md5)。解压后使用命令:"python.exe setup.py install" 进行安装,这种安装方式在Windows下也可行。当然也可以使用pip命令安装,省去下载安装包的过程:"pip install beautifulsoup4",但Windows下,要另外装pip工具。
BeautifulSoup初体验
from urllib.request import urlopen
from bs4 import BeautifulSoup
html = urlopen("http://www.pythonscraping.com/exercises/exercise1.html")
bsObj = BeautifulSoup(html.read(), "html.parser");
print(bsObj.h1)
这段代码解析了exercise1.html这个HTML文件,并输出了h1这个字段的内容:
HTML文件的结构<h1>An Interesting Title<h1>
上面这个图显示的是HTML的常用结构。bsObj.h1是一个快捷的访问h1数据的方法,实际上类似这样的访问也是有效的:bsObj.html.body.h1,bsObj.body.h1,bsObj.html.h1
通过这个例子,我们应该可以体会到BeautifulSoup的方便。第三章将对BeautifulSoup做更深入的讨论,例如:使用正则表达式提取网页数据。
考虑脚本的稳定性
try:
html = urlopen("http://www.pythonscraping.com/exercises/exercise1.html")
except HTTPError as e:
print(e)
#return null, break, or do some other "Plan B"
else:
#program continues. Note: If you return or break in the
#exception catch, you do not need to use the "else" statement
考虑到某些情况下会出现页面无法访问的问题,建议加上以上出错判断的代码。如果尝试访问的BeautifulSoup标签不存在,BeautifulSoup会返回None对象。那么问题来了,访问None对象会抛出AttributeError异常。以下是一个鲁棒的获取数据的脚本:
容错的脚本例子第二章 HTML解析的进阶
第一章介绍的BeautifulSoup可以很方便地提取需要的带标签的数据,但随着标记深度的递增,简单地使用对象数据不利于表达和调试,例如以下代码就很难理解:
bsObj.findAll("table")[4].findAll("tr")[2].find("td").findAll("div")[1].find("a")
这段代码不仅不美观,还不够鲁棒。当爬取的网站做了小幅度的更改后,这段代码就无效了。有没有更好的方法呢?
BeautifulSoup的另一个功能
通常,一个HTML页面都包含有CSS样式,例如
<span class="green"></span>
<span class="red"></span>
BeautifulSoup可以通过制定class的值,过滤一些不需要的内容。例如
nameList=bsObj.findAll("span", {"class":"green"})
for name in nameList:
print(name.get_text())
这句代码可以获得class为green的span内容。其中函数get_text()可以获得标签的内容。
findAll和find的函数定义如下
findAll(tag,attributes,recursive,text,limit,keywords)
find(tag,attributes,recursive,text,keywords)
findAll还有很多有用的写法
.findAll({"h1","h2","h3","h4","h5","h6"})
.findAll("span", {"class":"green","class":"red"})
这些代码可以列出所有有关的标签内容。recursive设置为true的话就执行递归查询。默认情况该值为true。
nameList=bsObj.findAll(text="the prince")
print(len(nameList))
以上的代码可以统计"the prince"字符串出现的次数,输出为7。(真是太强大了)
allText=bsObj.findAll(id="text") #和bsObj.findAll("", {"id":"text"})是等价的
print(allText[0].get_text())
这段代码可以根据attribute来选择内容,代码的输出是id是text的div包含的所有文本内容。因为class是Python的关键字,bsObj.findAll(class="green")是不允许的,可以用以下代码替换:
bsObj.findAll(class_="green")
bsObj.findAll("", {"class":"green"}
正则表达式
正则表达式在很多人眼里都是个高大上的工具,什么都不多说了,先来一个例子。
aa*bbbbb(cc)*(d | )
aa*
这里的*指代任何东西
bbbbb
代表5个b,没有其它的意义
(cc)*
代表任意个重复的c,也可以是0个
(d | )
|代表或的意思,这句代表以d和空格结尾,或者仅仅以空格结尾
一些通用的规则如下,例如E-mail的命名规则:
E-mail能包含的字符为:大小写字母、数字、点、加号或者下划线,并且每个E-mail都要包含@符号。这个规则用正则表达式可以这样写:[A-Za-z0-9\._+]+
正则表达式非常得智能,它会知道中括号中的内容是指从A到Z的字符都可以,\.代表一个点(period),然后最后的+号以为着这些字符可以出现任意多次,但至少要出现一次。
再看一个更复杂的:[A-Za-z0-9\._+]+@[A-Za-z]+\.(com|org|edu|net),这种正则表达式就可以匹配任意以com|org|edu|net结尾的邮箱地址。
接下来详细介绍12个在Python中常用的正则表达式
.代表任意一个字符,如果是要查找点的话,就用转义字符\.
+的作用是将前面一个字符或一个子表达式重复一遍或者多遍。
*跟在其他符号后面表达可以匹配到它0次或多次,例如https*就可以找出http://和https://两种。
这里举一个例子介绍正则表达式在实际数据抓取,原书的代码编译不过,我做了一些修改。
from urllib.request import urlopen
from bs4 import BeautifulSoup
import re
html = urlopen("http://www.pythonscraping.com/pages/page3.html")
bsObj = BeautifulSoup(html.read(), "html.parser")
images = bsObj.findAll("img", {"src":re.compile("\.\.\/img\/gifts\/img.*\.jpg")})
for image in images:
print(image["src"])
访问属性
通过这样的方式就可以访问某个属性:myImgTag.attrs['src']
Lambda表达式
作者针对Lambda的描述是某个函数的参数是另一个函数。我的理解是,某个查找的条件是某个判断函数的返回值。例如:
bsObj.findAll(lambda tag: len(tag.attrs) == 2)
这句代码可以找出tag有两个的条目。返回的是len(tag.attrs) == 2为True的所有条目。
感觉这两章的内容足够应付基本的爬虫应用了,以后有额外的需求,再解读其他几章。^__^