python爬虫-基础
所谓网页抓取,就是把URL地址中指定的网络资源从网络流中读取出来,保存到本地。
类似于使用程序模拟IE浏览器的功能,把URL作为HTTP请求的内容发送到服务器端, 然后读取服务器端的响应资源。
1、浏览网页的过程
抓取网页的过程其实和读者平时使用IE浏览器浏览网页的道理是一样的。
比如说你在浏览器的地址栏中输入 www.baidu.com 这个地址。
打开网页的过程其实就是浏览器作为一个浏览的“客户端”,向服务器端发送了 一次请求,把服务器端的文件“抓”到本地,再进行解释、展现。
HTML是一种标记语言,用标签标记内容并加以解析和区分。
2 、URL与URI
2.1 URI
Web上每种可用的资源,如 HTML文档、图像、视频片段、程序等都由一个通用资源标志符(Universal Resource Identifier, URI)进行定位。
URI通常由三部分组成:
①访问资源的命名机制;
②存放资源的主机名;
③资源自身 的名称,由路径表示。
如下面的URI:
http://www.why.com.cn/myhtml/html1223/
我们可以这样解释它:
①这是一个可以通过HTTP协议访问的资源,
②位于主机 www.why.com.cn上,
③通过路径“/myhtml/html1223/”访问。
2.2 URL
URL是URI的一个子集。它是Uniform Resource Locator的缩写,译为“统一资源定位 符”。
通俗地说,URL是Internet上描述信息资源的字符串,主要用在各种WWW客户程序和服务器程序上。
采用URL可以用一种统一的格式来描述各种信息资源,包括文件、服务器的地址和目录等。
URL的一般格式为(带方括号[]的为可选项):
protocol :// hostname[:port] / path / [;parameters][?query]#fragment
URL的格式由三部分组成:
①第一部分是协议(或称为服务方式)。
②第二部分是存有该资源的主机IP地址(有时也包括端口号)。
③第三部分是主机资源的具体地址,如目录和文件名等。
第一部分和第二部分用“://”符号隔开,
第二部分和第三部分用“/”符号隔开。
第一部分和第二部分是不可缺少的,第三部分有时可以省略。
2.3 比较
URI属于URL更低层次的抽象,一种字符串文本标准。
换句话说,URI属于父类,而URL属于URI的子类。URL是URI的一个子集。
URI的定义是:统一资源标识符;
URL的定义是:统一资源定位符。
二者的区别在于,URI表示请求服务器的路径,定义这么一个资源。
而URL同时说明要如何访问这个资源(http://)。
2.4 URL示例
http协议
使用超级文本传输协议HTTP,提供超级文本信息服务的资源。
例:http://www.peopledaily.com.cn/channel/welcome.htm
其计算机域名为www.peopledaily.com.cn。
超级文本文件(文件类型为.html)是在目录 /channel下的welcome.htm。
这是中国人民日报的一台计算机。
例:http://www.rol.cn.net/talk/talk1.htm
其计算机域名为www.rol.cn.net。
超级文本文件(文件类型为.html)是在目录/talk下的talk1.htm。
这是瑞得聊天室的地址,可由此进入瑞得聊天室的第1室。
文件
用URL表示文件时,服务器方式用file表示,后面要有主机IP地址、文件的存取路 径(即目录)和文件名等信息。
有时可以省略目录和文件名,但“/”符号不能省略。
例:file://ftp.yoyodyne.com/pub/files/foobar.txt
上面这个URL代表存放在主机ftp.yoyodyne.com上的pub/files/目录下的一个文件,文件名是foobar.txt。
例:file://ftp.yoyodyne.com/pub
代表主机ftp.yoyodyne.com上的目录/pub。
例:file://ftp.yoyodyne.com/
代表主机ftp.yoyodyne.com的根目录。
3、urllib2
urllib2是Python的一个获取URLs(Uniform Resource Locators)的组件。
它以urlopen函数的形式提供了一个非常简单的接口。
response = urllib2.urlopen('http://www.baidu.com/') html = response.read() print html #打印的即是网页的源码
urllib2用一个Request对象来映射你提出的HTTP请求。
在它最简单的使用形式中你将用你要请求的地址创建一个Request对象,
通过调用urlopen并传入Request对象,将返回一个相关请求response对象,
这个应答对象如同一个文件对象,所以你可以在Response中调用.read()。
req = urllib2.Request('http://www.baidu.com') #处理http头 req2 = urllib2.Request('ftp://example.com/') #处理ftp头,urllib2使用相同的接口处理所有的URL头。 response = urllib2.urlopen(req) the_page = response.read() print the_page
在HTTP请求时,允许你做额外的两件事。
(1)发送data表单数据有时候你希望发送一些数据到URL(通常URL与CGI[通用网关接口]脚本,或其他WEB应用程序挂接)。
在HTTP中,这个经常使用熟知的POST请求发送。
这个通常在你提交一个HTML表单时由你的浏览器来做。
并不是所有的POSTs都来源于表单,你能够使用POST提交任意的数据到你自己的程序。
一般的HTML表单,data需要编码成标准形式。然后做为data参数传到Request对象。
编码工作使用urllib的函数而非urllib2。
import urllib import urllib2 url = 'http://www.someserver.com/register.cgi' values = {'name': 'WHY', 'location': 'SDU', 'language': 'Python'} data = urllib.urlencode(values) # 编码工作 req = urllib2.Request(url, data) # 发送请求同时传data表单 response = urllib2.urlopen(req) # 接受反馈的信息 the_page = response.read() # 读取反馈的内容
如果没有传送data参数,urllib2使用GET方式的请求。
GET和POST请求的不同之处是POST请求通常有"副作用",
它们会由于某种途径改变系统状态(例如提交成堆垃圾到你的门口)。
Data同样可以通过在Get请求的URL本身上面编码来传送。
(2)设置Headers到http请求
有一些站点不喜欢被程序(非人为访问)访问,或者发送不同版本的内容到不同的浏览器。
默认的urllib2把自己作为“Python-urllib/x.y”(x和y是Python主版本和次版本号,例如Python-urllib/2.7),
这个身份可能会让站点迷惑,或者干脆不工作。
浏览器确认自己身份是通过User-Agent头,当你创建了一个请求对象,你可以给他一个包含头数据的字典。
urlopen返回的应答对象response(或者HTTPError实例)有两个很有用的方法info()和geturl()
3.1 geturl()
这个返回获取的真实的URL,这个很有用,因为urlopen(或者opener对象使用的)或许会有重定向。获取的URL或许跟请求URL不同。
from urllib2 import Request, urlopen old_url = 'http://i.baidu.com/?from=image' req = Request(old_url) response = urlopen(req) print 'Old url :' + old_url print 'Real url :' + response.geturl() 输出: Old url :http://i.baidu.com/?from=image Real url :http://i.baidu.com/welcome/
3.2 info()
这个返回对象的字典对象,该字典描述了获取的页面情况。通常是服务器发送的特定头headers。目前是httplib.HTTPMessage 实例。
经典的headers包含"Content-length","Content-type",和其他内容。
from urllib2 import Request, urlopen old_url = 'http://www.baidu.com' req = Request(old_url) response = urlopen(req) print 'Info():' print response.info() 输出: Info(): Date: Tue, 14 Nov 2017 07:55:18 GMT Content-Type: text/html; charset=utf-8 Transfer-Encoding: chunked Connection: Close Vary: Accept-Encoding Set-Cookie: BAIDUID=6AD1DC5A0A2202A069705254C2CE007E:FG=1; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com Set-Cookie: BIDUPSID=6AD1DC5A0A2202A069705254C2CE007E; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com Set-Cookie: PSTM=1510646118; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com Set-Cookie: BDSVRTM=0; path=/ Set-Cookie: BD_HOME=0; path=/ Set-Cookie: H_PS_PSSID=1450_21102_24880_22075; path=/; domain=.baidu.com P3P: CP=" OTI DSP COR IVA OUR IND COM " Cache-Control: private Cxy_all: baidu+164eb539e96e551026e6b365b859f30e Expires: Tue, 14 Nov 2017 07:54:57 GMT X-Powered-By: HPHP Server: BWS/1.1 X-UA-Compatible: IE=Edge,chrome=1 BDPAGETYPE: 1 BDQID: 0xb02af5550000a934 BDUSERID: 0
3.3 openers/handles
Openers:
当你获取一个URL你使用一个opener(一个urllib2.OpenerDirector的实例)。
正常情况下,我们使用默认opener:通过urlopen。
但你能够创建个性的openers。
Handles:
Openers使用处理器handlers,所有的“繁重”工作由handlers处理。
每个handlers知道如何通过特定协议打开URLs,或者如何处理URL打开时的各个方面。
例如HTTP重定向或者HTTP cookies。
如果你希望用特定处理器获取URLs你会想创建一个openers,例如获取一个能处理cookie的opener,或者获取一个不重定向的opener。
要创建一个 opener,可以实例化一个OpenerDirector,然后调用.add_handler(some_handler_instance)。
同样,可以使用build_opener,这是一个更加方便的函数,用来创建opener对象,他只需要一次函数调用。
build_opener默认添加几个处理器,但提供快捷的方法来添加或更新默认处理器。
其他的处理器handlers你或许会希望处理代理,验证,和其他常用但有点特殊的情况。
install_opener 用来创建(全局)默认opener。这个表示调用urlopen将使用你安装的opener。
Opener对象有一个open方法。该方法可以像urlopen函数那样直接用来获取urls:通常不必调用install_opener,除了为了方便。
4、异常处理
当urlopen不能够处理一个response时,产生urlError。
不过通常的Python APIs异常如ValueError,TypeError等也会同时产生。
HTTPError是urlError的子类,通常在特定HTTP URLs中产生。
4.1 URLError
通常,URLError在没有网络连接(没有路由到特定服务器),或者服务器不存在的情况下产生。
这种情况下,异常同样会带有"reason"属性,它是一个tuple(可以理解为不可变的数组),
包含了一个错误号和一个错误信息。
import urllib2 req = urllib2.Request('http://www.baibai.com') try: urllib2.urlopen(req) except urllib2.URLError, e: print e.reason 输出: [Errno 11002] getaddrinfo failed #错误号是11002,内容是getaddrinfo failed
4.2 HTTPError
服务器上每一个HTTP 应答对象response包含一个数字"状态码"。
有时状态码指出服务器无法完成请求。默认的处理器会为你处理一部分这种应答。
例如:假如response是一个"重定向",需要客户端从别的地址获取文档,urllib2将为你处理。
其他不能处理的,urlopen会产生一个HTTPError。
典型的错误包含"404"(页面无法找到),"403"(请求禁止),和"401"(带验证请求)。
HTTP状态码表示HTTP协议所返回的响应的状态。
比如客户端向服务器发送请求,如果成功地获得请求的资源,则返回的状态码为200,表示响应成功。
如果请求的资源不存在, 则通常返回404错误。
HTTP状态码通常分为5种类型,分别以1~5五个数字开头,由3位整数组成:
200 请求成功 处理方式:获得响应的内容,进行处理 201 请求完成,结果是创建了新资源。新创建资源的URI可在响应的实体中得到 处理方式:爬虫中不会遇到 202 请求被接受,但处理尚未完成 处理方式:阻塞等待 204 服务器端已经实现了请求,但是没有返回新的信 息。如果客户是用户代理,则无须为此更新自身的文档视图。 处理方式:丢弃 300 该状态码不被HTTP/1.0的应用程序直接使用, 只是作为3XX类型回应的默认解释。存在多个可用的被请求资源。 处理方式:若程序中能够处理,则进行进一步处理,如果程序中不能处理,则丢弃 301 请求到的资源都会分配一个永久的URL,这样就可以在将来通过该URL来访问此资源 处理方式:重定向到分配的URL 302 请求到的资源在一个不同的URL处临时保存 处理方式:重定向到临时的URL 304 请求的资源未更新 处理方式:丢弃 400 非法请求 处理方式:丢弃 401 未授权 处理方式:丢弃 403 禁止 处理方式:丢弃 404 没有找到 处理方式:丢弃 5XX 回应代码以“5”开头的状态码表示服务器端发现自己出现错误,不能继续执行请求 处理方式:丢弃
Error Codes错误码
因为默认的处理器处理了重定向(300以外号码),并且100-299范围的号码指示成功,所以你只能看到400-599的错误号码。
BaseHTTPServer.BaseHTTPRequestHandler.response是一个很有用的应答号码字典,显示了HTTP协议使用的所有的应答号。
当一个错误号产生后,服务器返回一个HTTP错误号,和一个错误页面。
你可以使用HTTPError实例作为页面返回的应答对象response。
这表示和错误属性一样,它同样包含了read,geturl,和info方法。
import urllib2 req = urllib2.Request('http://bbs.csdn.net/callmewhy') try: urllib2.urlopen(req) except urllib2.URLError, e: print e.code #print e.read() 输出: 404
4.3 Wrapping
所以如果你想为HTTPError或URLError做准备,将有两个基本的办法。推荐使用第二种。
第一种:
from urllib2 import Request, urlopen, URLError, HTTPError req = Request('http://bbs.csdn.net/callmewhy') try: response = urlopen(req) except HTTPError, e: print 'The server couldn\'t fulfill the request.' print 'Error code: ', e.code except URLError, e: print 'We failed to reach a server.' print 'Reason: ', e.reason else: print 'No exception was raised.' 输出: The server couldn't fulfill the request. Error code: 404
因为HTTPError是URLError的子类,如果URLError在前面它会捕捉到所有的URLError(包括HTTPError )。
from urllib2 import Request, urlopen, URLError, HTTPError req = Request('http://bbs.csdn.net/callmewhy') try: response = urlopen(req) except URLError, e: if hasattr(e, 'code'): print 'The server couldn\'t fulfill the request.' print 'Error code: ', e.code elif hasattr(e, 'reason'): print 'We failed to reach a server.' print 'Reason: ', e.reason else: print 'No exception was raised.' 输出: The server couldn't fulfill the request. Error code: 404