python 网络爬虫(50页).doc
-python 网络爬虫-第 49 页抓取网页的含义和URL基本构成1、网络爬虫的定义网络爬虫,即Web Spider,是一个很形象的名字。把互联网比喻成一个蜘蛛网,那么Spider就是在网上爬来爬去的蜘蛛。网络蜘蛛是通过网页的链接地址来寻找网页的。从网站某一个页面(通常是首页)开始,读取网页的内容,找到在网页中的其它链接地址,然后通过这些链接地址寻找下一个网页,这样一直循环下去,直到把这个网站所有的网页都抓取完为止。如果把整个互联网当成一个网站,那么网络蜘蛛就可以用这个原理把互联网上所有的网页都抓取下来。这样看来,网络爬虫就是一个爬行程序,一个抓取网页的程序。网络爬虫的基本操作是抓取网页。那么如何才能随心所欲地获得自己想要的页面?我们先从URL开始。2、浏览网页的过程抓取网页的过程其实和读者平时使用IE浏览器浏览网页的道理是一样的。比如说你在浏览器的地址栏中输入 这个地址。打开网页的过程其实就是浏览器作为一个浏览的“客户端”,向服务器端发送了 一次请求,把服务器端的文件“抓”到本地,再进行解释、展现。HTML是一种标记语言,用标签标记内容并加以解析和区分。浏览器的功能是将获取到的HTML代码进行解析,然后将原始的代码转变成我们直接看到的网站页面。3、URI的概念和举例简单的来讲,URL就是在浏览器端输入的 这个字符串。在理解URL之前,首先要理解URI的概念。什么是URI?Web上每种可用的资源,如 HTML文档、图像、视频片段、程序等都由一个通用资源标志符(Universal Resource Identifier, URI)进行定位。 URI通常由三部分组成:访问资源的命名机制;存放资源的主机名;资源自身 的名称,由路径表示。如下面的URI:我们可以这样解释它:这是一个可以通过HTTP协议访问的资源,位于主机 上,通过路径“/html/html40”访问。 4、URL的理解和举例URL是URI的一个子集。它是Uniform Resource Locator的缩写,译为“统一资源定位 符”。通俗地说,URL是Internet上描述信息资源的字符串,主要用在各种WWW客户程序和服务器程序上。采用URL可以用一种统一的格式来描述各种信息资源,包括文件、服务器的地址和目录等。URL的格式由三部分组成: 第一部分是协议(或称为服务方式)。第二部分是存有该资源的主机IP地址(有时也包括端口号)。第三部分是主机资源的具体地址,如目录和文件名等。第一部分和第二部分用“:/”符号隔开,第二部分和第三部分用“/”符号隔开。第一部分和第二部分是不可缺少的,第三部分有时可以省略。 下面来看看两个URL的小例子。1.HTTP协议的URL示例:使用超级文本传输协议HTTP,提供超级文本信息服务的资源。 例: 其计算机域名为。超级文本文件(文件类型为.html)是在目录 /channel下的welcome.htm。这是中国人民日报的一台计算机。 例: 其计算机域名为。超级文本文件(文件类型为.html)是在目录/talk下的talk1.htm。这是瑞得聊天室的地址,可由此进入瑞得聊天室的第1室。2文件的URL用URL表示文件时,服务器方式用file表示,后面要有主机IP地址、文件的存取路 径(即目录)和文件名等信息。有时可以省略目录和文件名,但“/”符号不能省略。 例:file:/ 上面这个URL代表存放在主机上的pub/files/目录下的一个文件,文件名是foobar.txt。例:file:/ 代表主机上的目录/pub。 例:file:/ 代表主机的根目录。 爬虫最主要的处理对象就是URL,它根据URL地址取得所需要的文件内容,然后对它 进行进一步的处理。因此,准确地理解URL对理解网络爬虫至关重要。利用urllib2通过指定的URL抓取网页内容所谓网页抓取,就是把URL地址中指定的网络资源从网络流中读取出来,保存到本地。 类似于使用程序模拟IE浏览器的功能,把URL作为HTTP请求的内容发送到服务器端, 然后读取服务器端的响应资源。在Python中,我们使用urllib2这个组件来抓取网页。urllib2是Python的一个获取URLs(Uniform Resource Locators)的组件。它以urlopen函数的形式提供了一个非常简单的接口。最简单的urllib2的应用代码只需要四行。我们新建一个文件urllib2_test01.py来感受一下urllib2的作用:python view plaincopy1. import urllib2 2. response = urllib2.urlopen(' 3. html = response.read() 4. print html 按下F5可以看到运行的结果:我们可以打开百度主页,右击,选择查看源代码(火狐OR谷歌浏览器均可),会发现也是完全一样的内容。也就是说,上面这四行代码将我们访问百度时浏览器收到的代码们全部打印了出来。这就是一个最简单的urllib2的例子。除了"http:",URL同样可以使用"ftp:","file:"等等来替代。HTTP是基于请求和应答机制的:客户端提出请求,服务端提供应答。urllib2用一个Request对象来映射你提出的HTTP请求。在它最简单的使用形式中你将用你要请求的地址创建一个Request对象,通过调用urlopen并传入Request对象,将返回一个相关请求response对象,这个应答对象如同一个文件对象,所以你可以在Response中调用.read()。我们新建一个文件urllib2_test02.py来感受一下:python view plaincopy1. import urllib2 2. req = urllib2.Request('') 3. response = urllib2.urlopen(req) 4. the_page = response.read() 5. print the_page 可以看到输出的内容和test01是一样的。urllib2使用相同的接口处理所有的URL头。例如你可以像下面那样创建一个ftp请求。python view plaincopy1. req = urllib2.Request('ftp:/ 在HTTP请求时,允许你做额外的两件事。1.发送data表单数据这个内容相信做过Web端的都不会陌生,有时候你希望发送一些数据到URL(通常URL与CGI通用网关接口脚本,或其他WEB应用程序挂接)。在HTTP中,这个经常使用熟知的POST请求发送。这个通常在你提交一个HTML表单时由你的浏览器来做。并不是所有的POSTs都来源于表单,你能够使用POST提交任意的数据到你自己的程序。一般的HTML表单,data需要编码成标准形式。然后做为data参数传到Request对象。编码工作使用urllib的函数而非urllib2。我们新建一个文件urllib2_test03.py来感受一下:python view plaincopy1. import urllib 2. import urllib2 3. url = ' 4. values = 'name' : 'WHY', 5. 'location' : 'SDU', 6. 'language' : 'Python' 7. data = urllib.urlencode(values) # 编码工作 8. req = urllib2.Request(url, data) # 发送请求同时传data表单 9. response = urllib2.urlopen(req) #接受反馈的信息 10. the_page = response.read() #读取反馈的内容 如果没有传送data参数,urllib2使用GET方式的请求。GET和POST请求的不同之处是POST请求通常有"副作用",它们会由于某种途径改变系统状态(例如提交成堆垃圾到你的门口)。Data同样可以通过在Get请求的URL本身上面编码来传送。python view plaincopy1. import urllib2 2. import urllib 3. data = 4. data'name' = 'WHY' 5. data'location' = 'SDU' 6. data'language' = 'Python' 7. url_values = urllib.urlencode(data) 8. print url_values 9. name=Somebody+Here&language=Python&location=Northampton 10. url = ' 11. full_url = url + '?' + url_values 12. data = urllib2.open(full_url) 这样就实现了Data数据的Get传送。2.设置Headers到http请求有一些站点不喜欢被程序(非人为访问)访问,或者发送不同版本的内容到不同的浏览器。默认的urllib2把自己作为“Python-urllib/x.y”(x和y是Python主版本和次版本号,例如Python-urllib/2.7),这个身份可能会让站点迷惑,或者干脆不工作。浏览器确认自己身份是通过User-Agent头,当你创建了一个请求对象,你可以给他一个包含头数据的字典。下面的例子发送跟上面一样的内容,但把自身模拟成Internet Explorer。python view plaincopy1. import urllib 2. import urllib2 3. url = ' 4. user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)' 5. values = 'name' : 'WHY', 6. 'location' : 'SDU', 7. 'language' : 'Python' 8. headers = 'User-Agent' : user_agent 9. data = urllib.urlencode(values) 10. req = urllib2.Request(url, data, headers) 11. response = urllib2.urlopen(req) 12. the_page = response.read() 异常的处理和HTTP状态码的分类先来说一说HTTP的异常处理问题。当urlopen不能够处理一个response时,产生urlError。不过通常的Python APIs异常如ValueError,TypeError等也会同时产生。HTTPError是urlError的子类,通常在特定HTTP URLs中产生。 1.URLError通常,URLError在没有网络连接(没有路由到特定服务器),或者服务器不存在的情况下产生。这种情况下,异常同样会带有"reason"属性,它是一个tuple(可以理解为不可变的数组),包含了一个错误号和一个错误信息。我们建一个urllib2_test06.py来感受一下异常的处理:python view plaincopy1. import urllib2 2. req = urllib2.Request('') 3. try: urllib2.urlopen(req) 4. except urllib2.URLError, e: 5. print e.reason 按下F5,可以看到打印出来的内容是:Errno 11001 getaddrinfo failed也就是说,错误号是11001,内容是getaddrinfo failed服务器上每一个HTTP 应答对象response包含一个数字"状态码"。有时状态码指出服务器无法完成请求。默认的处理器会为你处理一部分这种应答。例如:假如response是一个"重定向",需要客户端从别的地址获取文档,urllib2将为你处理。其他不能处理的,urlopen会产生一个HTTPError。典型的错误包含"404"(页面无法找到),"403"(请求禁止),和"401"(带验证请求)。HTTP状态码表示HTTP协议所返回的响应的状态。比如客户端向服务器发送请求,如果成功地获得请求的资源,则返回的状态码为200,表示响应成功。如果请求的资源不存在, 则通常返回404错误。 HTTP状态码通常分为5种类型,分别以15五个数字开头,由3位整数组成:200:请求成功 处理方式:获得响应的内容,进行处理 201:请求完成,结果是创建了新资源。新创建资源的URI可在响应的实体中得到 处理方式:爬虫中不会遇到 202:请求被接受,但处理尚未完成 处理方式:阻塞等待 204:服务器端已经实现了请求,但是没有返回新的信 息。如果客户是用户代理,则无须为此更新自身的文档视图。 处理方式:丢弃300:该状态码不被HTTP/1.0的应用程序直接使用, 只是作为3XX类型回应的默认解释。存在多个可用的被请求资源。 处理方式:若程序中能够处理,则进行进一步处理,如果程序中不能处理,则丢弃301:请求到的资源都会分配一个永久的URL,这样就可以在将来通过该URL来访问此资源 处理方式:重定向到分配的URL302:请求到的资源在一个不同的URL处临时保存 处理方式:重定向到临时的URL 304 请求的资源未更新 处理方式:丢弃 400 非法请求 处理方式:丢弃 401 未授权 处理方式:丢弃 403 禁止 处理方式:丢弃 404 没有找到 处理方式:丢弃 5XX 回应代码以“5”开头的状态码表示服务器端发现自己出现错误,不能继续执行请求 处理方式:丢弃HTTPError实例产生后会有一个整型'code'属性,是服务器发送的相关错误号。Error Codes错误码因为默认的处理器处理了重定向(300以外号码),并且100-299范围的号码指示成功,所以你只能看到400-599的错误号码。BaseHTTPServer.BaseHTTPRequestHandler.response是一个很有用的应答号码字典,显示了HTTP协议使用的所有的应答号。当一个错误号产生后,服务器返回一个HTTP错误号,和一个错误页面。你可以使用HTTPError实例作为页面返回的应答对象response。这表示和错误属性一样,它同样包含了read,geturl,和info方法。我们建一个urllib2_test07.py来感受一下:python view plaincopy1. import urllib2 2. req = urllib2.Request(' 3. try: 4. urllib2.urlopen(req) 5. except urllib2.URLError, e: 6. print e.code 7. #print e.read() 按下F5可以看见输出了404的错误码,也就说没有找到这个页面。所以如果你想为HTTPError或URLError做准备,将有两个基本的办法。推荐使用第二种。我们建一个urllib2_test08.py来示范一下第一种异常处理的方案:python view plaincopy1. from urllib2 import Request, urlopen, URLError, HTTPError 2. req = Request(' 3. try: 4. response = urlopen(req) 5. except HTTPError, e: 6. print 'The server couldn't fulfill the request.' 7. print 'Error code: ', e.code 8. except URLError, e: 9. print 'We failed to reach a server.' 10. print 'Reason: ', e.reason 11. else: 12. print 'No exception was raised.' 13. # everything is fine 和其他语言相似,try之后捕获异常并且将其内容打印出来。这里要注意的一点,except HTTPError 必须在第一个,否则except URLError将同样接受到HTTPError 。因为HTTPError是URLError的子类,如果URLError在前面它会捕捉到所有的URLError(包括HTTPError )。我们建一个urllib2_test09.py来示范一下第二种异常处理的方案:python view plaincopy1. from urllib2 import Request, urlopen, URLError, HTTPError 2. req = Request(' 3. try: 4. response = urlopen(req) 5. except URLError, e: 6. if hasattr(e, 'reason'): 7. print 'We failed to reach a server.' 8. print 'Reason: ', e.reason 9. elif hasattr(e, 'code'): 10. print 'The server couldn't fulfill the request.' 11. print 'Error code: ', e.code 12. else: 13. print 'No exception was raised.' 14. # everything is fine Opener与Handler的介绍和实例应用在开始后面的内容之前,先来解释一下urllib2中的两个个方法:info and geturl urlopen返回的应答对象response(或者HTTPError实例)有两个很有用的方法info()和geturl()1.geturl():这个返回获取的真实的URL,这个很有用,因为urlopen(或者opener对象使用的)或许会有重定向。获取的URL或许跟请求URL不同。以人人中的一个超级链接为例,python view plaincopy1. from urllib2 import Request, urlopen, URLError, HTTPError 2. old_url = ' 3. req = Request(old_url) 4. response = urlopen(req) 5. print 'Old url :' + old_url 6. print 'Real url :' + response.geturl() 运行之后可以看到真正的链接指向的网址:2.info():这个返回对象的字典对象,该字典描述了获取的页面情况。通常是服务器发送的特定头headers。目前是httplib.HTTPMessage 实例。经典的headers包含"Content-length","Content-type",和其他内容。我们建一个urllib2_test11.py来测试一下info的应用:python view plaincopy1. from urllib2 import Request, urlopen, URLError, HTTPError 2. old_url = '' 3. req = Request(old_url) 4. response = urlopen(req) 5. print 'Info():' 6. print response.info() 运行的结果如下,可以看到页面的相关信息:下面来说一说urllib2中的两个重要概念:Openers和Handlers。1.Openers:当你获取一个URL你使用一个opener(一个urllib2.OpenerDirector的实例)。正常情况下,我们使用默认opener:通过urlopen。但你能够创建个性的openers。2.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,除了为了方便。说完了上面两个内容,下面我们来看一下基本认证的内容,这里会用到上面提及的Opener和Handler。Basic Authentication 基本验证为了展示创建和安装一个handler,我们将使用HTTPBasicAuthHandler。当需要基础验证时,服务器发送一个header(401错误码) 请求验证。这个指定了scheme 和一个realm,看起来像这样:Www-authenticate: SCHEME realm="REALM".例如Www-authenticate: Basic realm="cPanel Users"客户端必须使用新的请求,并在请求头里包含正确的姓名和密码。这是“基础验证”,为了简化这个过程,我们可以创建一个HTTPBasicAuthHandler的实例,并让opener使用这个handler就可以啦。HTTPBasicAuthHandler使用一个密码管理的对象来处理URLs和realms来映射用户名和密码。如果你知道realm(从服务器发送来的头里)是什么,你就能使用HTTPPasswordMgr。通常人们不关心realm是什么。那样的话,就能用方便的HTTPPasswordMgrWithDefaultRealm。这个将在你为URL指定一个默认的用户名和密码。这将在你为特定realm提供一个其他组合时得到提供。我们通过给realm参数指定None提供给add_password来指示这种情况。最高层次的URL是第一个要求验证的URL。你传给.add_password()更深层次的URLs将同样合适。说了这么多废话,下面来用一个例子演示一下上面说到的内容。我们建一个urllib2_test12.py来测试一下info的应用:python view plaincopy1. # -*- coding: utf-8 -*- 2. import urllib2 3. # 创建一个密码管理者 4. password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm() 5. # 添加用户名和密码 6. top_level_url = " 7. # 如果知道 realm, 我们可以使用他代替 None. 8. # password_mgr.add_password(None, top_level_url, username, password) 9. password_mgr.add_password(None, top_level_url,'why', '1223') 10. # 创建了一个新的handler 11. handler = urllib2.HTTPBasicAuthHandler(password_mgr) 12. # 创建 "opener" (OpenerDirector 实例) 13. opener = urllib2.build_opener(handler) 14. a_url = ' 15. # 使用 opener 获取一个URL 16. opener.open(a_url) 17. # 安装 opener. 18. # 现在所有调用 urllib2.urlopen 将用我们的 opener. 19. urllib2.install_opener(opener) 注意:以上的例子我们仅仅提供我们的HHTPBasicAuthHandler给build_opener。默认的openers有正常状况的handlers:ProxyHandler,UnknownHandler,HTTPHandler,HTTPDefaultErrorHandler, HTTPRedirectHandler,FTPHandler, FileHandler, HTTPErrorProcessor。代码中的top_level_url 实际上可以是完整URL(包含"http:",以及主机名及可选的端口号)。例如:也可以是一个“authority”(即主机名和可选的包含端口号)。例如:“” or “:8080”。后者包含了端口号。urllib2的使用细节与抓站技巧前面说到了urllib2的简单入门,下面整理了一部分urllib2的使用细节。1.Proxy 的设置urllib2 默认会使用环境变量 http_proxy 来设置 HTTP Proxy。如果想在程序中明确控制 Proxy 而不受环境变量的影响,可以使用代理。新建test14来实现一个简单的代理Demo:python view plaincopy1. import urllib2 2. enable_proxy = True 3. proxy_handler = urllib2.ProxyHandler("http" : 'http:/some-:8080') 4. null_proxy_handler = urllib2.ProxyHandler() 5. if enable_proxy: 6. opener = urllib2.build_opener(proxy_handler) 7. else: 8. opener = urllib2.build_opener(null_proxy_handler) 9. urllib2.install_opener(opener) 这里要注意的一个细节,使用 urllib2.install_opener() 会设置 urllib2 的全局 opener 。这样后面的使用会很方便,但不能做更细致的控制,比如想在程序中使用两个不同的 Proxy 设置等。比较好的做法是不使用 install_opener 去更改全局的设置,而只是直接调用 opener 的 open 方法代替全局的 urlopen 方法。2.Timeout 设置在老版 Python 中(Python2.6前),urllib2 的 API 并没有暴露 Timeout 的设置,要设置 Timeout 值,只能更改 Socket 的全局 Timeout 值。python view plaincopy1. import urllib2 2. import socket 3. socket.setdefaulttimeout(10) # 10 秒钟后超时 4. urllib2.socket.setdefaulttimeout(10) # 另一种方式 在 Python 2.6 以后,超时可以通过 urllib2.urlopen() 的 timeout 参数直接设置。python view plaincopy1. import urllib2 2. response = urllib2.urlopen('', timeout=10) 3.在 HTTP Request 中加入特定的 Header要加入 header,需要使用 Request 对象:python view plaincopy1. import urllib2 2. request = urllib2.Request(' 3. request.add_header('User-Agent', 'fake-client') 4. response = urllib2.urlopen(request) 5. print response.read() 对有些 header 要特别留意,服务器会针对这些 header 做检查User-Agent : 有些服务器或 Proxy 会通过该值来判断是否是浏览器发出的请求Content-Type : 在使用 REST 接口时,服务器会检查该值,用来确定 HTTP Body 中的内容该怎样解析。常见的取值有:application/xml : 在 XML RPC,如 RESTful/SOAP 调用时使用application/json : 在 JSON RPC 调用时使用application/x-www-form-urlencoded : 浏览器提交 Web 表单时使用在使用服务器提供的 RESTful 或 SOAP 服务时, Content-Type 设置错误会导致服务器拒绝服务urllib2 默认情况下会针对 HTTP 3XX 返回码自动进行 redirect 动作,无需人工配置。要检测是否发生了 redirect 动作,只要检查一下 Response 的 URL 和 Request 的 URL 是否一致就可以了。python view plaincopy1. import urllib2 2. my_url = '' 3. response = urllib2.urlopen(my_url) 4. redirected = response.geturl() = my_url 5. print redirected 6. my_url = ' 7. response = urllib2.urlopen(my_url) 8. redirected = response.geturl() = my_url 9. print redirected 如果不想自动 redirect,除了使用更