class 14 爬虫基础

 Spider简介: 

  可以分成简单的几步:抓取页面,分析页面和存储数据(主要用到的库有requests,Selenium,aiohttp)

解决JavaScrip渲染问题

  • 分析Ajax请求
  • Selenium/ WebDriver
  • Splash
  • PyV8、Ghost.py

 解析方式

  • 直接处理
  • Json解析
  • 正则表达式
  • BeautifulSoup
  • PyQuery
  • XPath

 爬取数据类型

  • 网页文本:如HTML、JSON格式文本等
  • 图片:获取到的是二进制文件,保存为图片格式
  • 视频:同为二进制文件,保存为视频格式
  • 其他:只要能请求到的,都能获取

保存数据

  • 文本:纯文本、Json、Xml等
  • 关系型数据库:如MySQL、Oracle、SQL Server等具有结构化表结构形式存储
  • 非关系型数据库:MongoDB、Redis等Key-Value形式存储。
  • 二进制文件:图片、视频、音频等直接保存成特定格式即可。

 选择器:

  • 网页由一个个节点组成, CSS选择器会根据不同的节点设置不同的样式规则:

    • 在 css 中,使用 css 选择器来定位节点。 例:  div 节点的 id 为 container,那么就可以表示为#container,其中#开头代表选择 id其后紧跟 id 的名称
    • 如果想选择 class 为 wrapper 的节点,便可以使用.wrapper,这里以点(.)开头代表选择 class ,其后紧跟 class 的名称

    • 根据标签名筛选,例如想选择二级标题,直接用 h2 即可。 这是最常用的 3 种表示,分别是根据id、class、标签名筛选,请牢记它们的写法。

  • CSS 选择器还支持嵌套选择,
    • 各个选择器之间加上空格分隔开便可以代表嵌套关系, 如 #container .wrapper p 则代表先选择 id 为 container 的节点,然后选中其内部的 class 为 wrapper 的节 点,然后再进一步选中其内部的 p 节点
    • 如果不加空格,则代表并列关系,如 div#container .wrapper p .text 代表先选择 id 为 container 的 div 节点,然后选中其内部的 class 为 wrapper 的节点,再进一 步选中其内部的 class 为 text 的 p 节点。 这就是 css 选择器,其筛选功能还是非常强大的。
      • CSS 选择器还有一些其他语法规则,具体如表 2-4 所示:
      •   
        选择器例子例子描述CSS
        .class .intro 选择 class="intro" 的所有元素。 1
        #id #firstname 选择 id="firstname" 的所有元素。 1
        * * 选择所有元素。 2
        element p 选择所有 <p> 元素。 1
        element,element div,p 选择所有 <div> 元素和所有 <p> 元素。 1
        element element div p 选择 <div> 元素内部的所有 <p> 元素。 1
        element>element div>p 选择父元素为 <div> 元素的所有 <p> 元素。 2
        element+element div+p 选择紧接在 <div> 元素之后的所有 <p> 元素。 2
        [attribute] [target] 选择带有 target 属性所有元素。 2
        [attribute=value] [target=_blank] 选择 target="_blank" 的所有元素。 2
        [attribute~=value] [title~=flower] 选择 title 属性包含单词 "flower" 的所有元素。 2
        [attribute|=value] [lang|=en] 选择 lang 属性值以 "en" 开头的所有元素。 2
        :link a:link 选择所有未被访问的链接。 1
        :visited a:visited 选择所有已被访问的链接。 1
        :active a:active 选择活动链接。 1
        :hover a:hover 选择鼠标指针位于其上的链接。 1
        :focus input:focus 选择获得焦点的 input 元素。 2
        :first-letter p:first-letter 选择每个 <p> 元素的首字母。 1
        :first-line p:first-line 选择每个 <p> 元素的首行。 1
        :first-child p:first-child 选择属于父元素的第一个子元素的每个 <p> 元素。 2
        :before p:before 在每个 <p> 元素的内容之前插入内容。 2
        :after p:after 在每个 <p> 元素的内容之后插入内容。 2
        :lang(language) p:lang(it) 选择带有以 "it" 开头的 lang 属性值的每个 <p> 元素。 2
        element1~element2 p~ul 选择前面有 <p> 元素的每个 <ul> 元素。 3
        [attribute^=value] a[src^="https"] 选择其 src 属性值以 "https" 开头的每个 <a> 元素。 3
        [attribute$=value] a[src$=".pdf"] 选择其 src 属性以 ".pdf" 结尾的所有 <a> 元素。 3
        [attribute*=value] a[src*="abc"] 选择其 src 属性中包含 "abc" 子串的每个 <a> 元素。 3
        :first-of-type p:first-of-type 选择属于其父元素的首个 <p> 元素的每个 <p> 元素。 3
        :last-of-type p:last-of-type 选择属于其父元素的最后 <p> 元素的每个 <p> 元素。 3
        :only-of-type p:only-of-type 选择属于其父元素唯一的 <p> 元素的每个 <p> 元素。 3
        :only-child p:only-child 选择属于其父元素的唯一子元素的每个 <p> 元素。 3
        :nth-child(n) p:nth-child(2) 选择属于其父元素的第二个子元素的每个 <p> 元素。 3
        :nth-last-child(n) p:nth-last-child(2) 同上,从最后一个子元素开始计数。 3
        :nth-of-type(n) p:nth-of-type(2) 选择属于其父元素第二个 <p> 元素的每个 <p> 元素。 3
        :nth-last-of-type(n) p:nth-last-of-type(2) 同上,但是从最后一个子元素开始计数。 3
        :last-child p:last-child 选择属于其父元素最后一个子元素每个 <p> 元素。 3
        :root :root 选择文档的根元素。 3
        :empty p:empty 选择没有子元素的每个 <p> 元素(包括文本节点)。 3
        :target #news:target 选择当前活动的 #news 元素。 3
        :enabled input:enabled 选择每个启用的 <input> 元素。 3
        :disabled input:disabled 选择每个禁用的 <input> 元素 3
        :checked input:checked 选择每个被选中的 <input> 元素。 3
        :not(selector) :not(p) 选择非 <p> 元素的每个元素。 3
        ::selection ::selection 选择被用户选取的元素部分。 3

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

爬虫的基本原理:

  • 发起请求——> 获取响应内容——>解析内容——>保存数据
    • 发起请求:通过HTTP库向目标站点发起请求,即发送一个Request,请求可以包含额外的Headers等信息,等待服务器响应
    • 解析内容:得到的内容可能是HTML,通用正则表达式提取(万能方法)、网页解析库进行行解析。可能是Json,可以直接转为Json对象解析,可能是二进制数据,可以做保存或者进一步的处理。
    • 获取响应内容:如果服务器能正常响应,会得到一个Response,Response的内容  
    • 保存数据:保存形式多样可以存为文本,也可以保存至数据库(MySQL和MongoDB),或者保存特定格式的文件(TEXT,JSON)

Request与Response

  • 关系 
    • HTTP Request:浏览器发送消息给该网址所在的服务器
    •   HTTP Response:务器收到浏览器发送的消息后,能够根据浏览器发送的消息内容做相应处理,然后把消息回传给浏览器。
    • 浏览器收到服务器的response信息后,会对信息进行相应处理,然后展示。
  • HTTP请求过程:

    • Name: 请求的名称,一般会将URL的最后一部分内容当作名称
    • Status:响应的状态码,显示200,代表响应正常。通过状态码,可以判断发送请求后是否得到正常的响应
    • Type:请求的文档类型。这里问document,代表我们请求的是一个HTML文档,内容就是一些HTML代码
    • Initiator:请求源。用来标记请求是由哪个对象或进程发起的
    • Size:从服务器下载的文件和请求的资源大小。如果是从缓存中取的资源,则该列会显示from cache。 
    • Time:发起请求得到获取响应所用的总时间
    • Waterfall:网络请求的可视化瀑布流。
  • 请求(Request)
    • 请求由客户端想服务端发出,可以分为4个部分:请求方法(Request Method)、请求的网址(Request URL)、请求头(Request Headers)、请求体(Request Body)
  • 请求方法
    • 请求方式:常见的主要有GET、POST两种类型,另外还有HEAD、PUT、DELETE、OPTIONS等
      • GET和POST请求方法主要区别。

        • GET请求中的参数包含在URL里面,数据可以在URL中看到,而POST请求的URL不会包含这些数据,数据都是通过表单形式传输的,会包含在请求体中。

        • GET请求提交的数据最多只有1024字节,而POST方式没有限制。

    • 请求头:包含请求时的头部信息,如比较重要的信息有:Referer、User-Agent、Host、Cookies等信息
      • Accept:请求报头域,用于指定客户端可接受哪些类型的信息。
      • Accept-Language:指定客户端可接受的语言类型。
      • accept-Encoding:指定客户端可接受的内容编码。
      • Host:用于指定请求资源的主机IP和端口号,其内容为请求URL的冤死服务器或网关的位置。从HTTP1.1版本开始,请求必须包含此内容
      • Cookie:常用复数形式Cookies,这是网站为了辨别用户进行绘画跟踪而存储在用户本地的数据。它主要的功能是维持当前访问会话。
      • Referer:此内容用来标识这个请求是从哪个页面发过来的,服务器可以拿到这一信息并做相应的处理,如源统计,防盗链处理等
      • User-Agent:简称UA,它是一个特殊的字符串头,可以使服务器识别客户使用的操作系统及版本,浏览器及版本等信息。他在做爬虫加上此次信息,可以伪装为浏览器;如果不加,可能会识别为爬虫
      • Content-Type:也叫互联网媒体类型或者MIME类型,在HTTP协议消息头中,它用来表示具体请求中的媒体类型信息。如:text/html代表HTML格式,image/gif代表GIF图片,application/json代表JSON类型,更多对应关系可查看对照表:http://tool.oschina.net/commons 
    • 请求URL:URL全称统一资源定位符,如一个网页文档、一张图片、一个视频等都可以用URL唯一来确定
    • 请求体:请求时额外携带的数据。如表单提交时的表单数据
      • 只有设置Request Headers中指定Content-Type 为application/x-www-form-urlencoded,才会以表单数据的形式提交。另外,我们也可以将Content-Type设置为application/json来提交json数据,或者设置为multipart/form-data来上传文件。
      • Content-Type和POST提交数据方式的关系
        • application/x-www-form-urlencoded :  表单数据
        • multipart/form-data :表单文件上传
        • application/json :序列化JSON数据
        • text/xml : XML数据
    • 注意:爬虫中,如果要沟构造POST请求,需要使用正确的Content-Tpye,并了解各种请求库的各个参数设置时使用的是哪张Content-Type,不然会导致POST提交后无法正常响应
  • 响应(Response)
    • 响应由服务端返回给客户端,可以分为三部分:响应状态码(Response Status Code)、响应头(Response Headers)和响应体(Response Body)
  • Response
    • 响应状态:有多种响应状态,如200代表成功、301跳转、4040找不到网页...
    • 响应头:包含服务器对请求应答信息,如:content-type、server、set-cookies等。
      • Date:标识响应产生的时间。
      • Last-Modified:指定资源的最后修改时间
      • Content-Encoding:指定响应内容的编码
      • Server:包含服务器的信息,比如名称,版本号等。
      • Content-Type:文档类型,指定返回的数据类型是什么,如text/html代表返回HTML文档,application/x-javascript则代表JavaScript文件,image/jpeg则代表返回图片。
      • Set-Cookies:设置Cookies。响应头中的Set-Cookies告诉浏览器需要将此内容放在Cookies中,下次请求携带Cookies请求
      • Expires:指定响应的过期时间,可以使代理服务器或浏览器将加在的内容更新到缓存中。如果再次访问时,就可以直接从缓存中加载,降低服务器负载,缩短加载时间。
    • 响应体:最主要的部分,包含请求资源的内容,网页HTML、图片二进制数据等
    • Cookies属性结构:
      • Name:该Cookie的名称,一旦创建便不可更改
      • Value:该Cookie的值。如果值为Unicode字符,需要为字符编码。如果值为二进制数据,则需要使用BASE64编码
      • Domain:可以访问该Cookie的域名。例如,如果设置为.zhihu.com,则所有已zhihu.com结尾的域名都可以访问该Cookie
      • Max Age: 该Cookie失效时间,单位为秒,也场合Expires一起使用,通过它可以计算出有效时间。Max Age如果为正数,则该Cookie在Max Age秒之后失效。如果为负数,则关闭浏览器时Cookie即失效,浏览器也不会以任何形式保存该Cookie
      • Path:该Cookie的使用路径。如果设置为/Path/,则只有路径为/Path/的页面可以访问该Cookie。如果设置为/,则域名下的所有页面都可以访问该Cookie。
      • Size字段:此Cookie的大小
      • HTTP字段:Cookie的httponly属性。若此属性为true,则只有在HTTP头中会带有此Cookie信息,而不能通过document.cookie来访问此Cookie。
      • Secure:该Cookie是否仅被使用安全协议传输。安全协议有HTTPS和SSL等,在网络上传输数据之前先将数据加密。默认为false。
    • 会话Cookie:把Cookie放在浏览器内存里,浏览器在关闭之后该Cookie即失效
    • 持久Cookie:把Cookie保存到客户端硬盘里,下次还可以继续使用,用于长久保持用户登录状态。
    • 严格来讲,没有会话Cookie和持久cookie之分,只是由Cookie的Max Age或Expires字段决定过期时间。

代理的基本原理

  •  代理分类
    • 根据协议类型区分
      • FTP代理服务器:主要用于访问FTP服务器,一般有上传、下载以及缓存功能,端口一般为21、2121等
      • HTTP代理服务器:主要用于访问网页,一般有内容过滤和缓存功能,端口一般为80、8080、3128等
      • SSL/TLS代理:主要用于访问加密网站,一般有SSL或TLS加密功能(最高支持128位加密强度),端口一般为443
      • RTSP代理:主要用于访问R额按了流媒体服务器,一般有缓存功能,端口一般为554.
      • Telnet代理:主要用于telnet远程控制(黑客入侵计算机时常用于隐藏身份),端口一般为23.
      • POP3/STP代理:主要用于POP3/SMTP方式发邮件,一般有缓存功能,端口一般为110/25.
      • SOCKS代理:只是单纯传递数据包,不关心具体协议和用法,所以速度快很多,一般有缓存功能,端口一般为1 0 8 0。SOCKS代理协议又分为SOCKS4和SOCKS5,前者只支持TCP ,而后者支持TCP和UDP,还支持各种身份验证机制、服务器端域名解析等。简单来说,SOCKS4能做到的SOCKS5都可以做到,但SOCKS5能做到的SOCKS4不一定能做到
    • 根据匿名程度区分
      • 高度匿名代理:会将数据包原封不动地转发,在服务端看来就好像真的是一个普通客户端在访问,而记录的IP是代理服务器的IP
      • 普通匿名代理:会在数据包上做一些改动,服务端上有可能发现这是个代理服务器,也有一定几率追查到客户端的真实IP。代理服务器通常会加入的HTTP头有HTTP_VIA和HTTP_X_FORWARDED_FOR。
      • 透明代理:不但改动数据包,还会告诉服务器客户端的真实IP。这种代理除了能用缓存技术提高浏览速度,能用内容过滤提高安全性之外,并无其他显著作用。最常见的例子是内网中的硬件防火墙
      • 间谍代理:值组织或个人创建的用于记录用户传输的数据,然后进行研究、监控等目的的代理服务器。
  • 常见代理设置
    • 使用网上的免费代理:最好使用高匿名代理,另外可用的代理不多,需要在使用前筛选一下可用代理,也可以进一步维护一个代理池
    • 使用付费代理服务:互联网上存在许多代理商,可以付费使用,质量比免费代理好很多。
    • ADSL拨号:拨一次号换一次IP,稳定性高,也是一种比较有效的解决方案。

 三章:基本库的使用

  1.  使用urllib
    • urllib是python 内置的HTTP请求库,包含4个模块。库官方文档:https://docs.python.org/3/library/urllib.html
      • request:是最基本的HTTP请求模块,可以用来模拟发送请求。只需要给库方法传入URL以及额外的参数
      • error:异常处理模块,如果出现请求错误,可以捕获这些一场,进行重试或其他操作以保证长须不会意外终止。
      • parse:一个工具模块,提供许多URL处理方法,如:拆分/解析/合并等
      • robotparser:主要用来识别网站的robots.txt文件,然后判断哪些网站可以爬,哪些网站不可以爬,使用比较少。
    1. 发送请求 
      • 使用urllib的request模块,可以方便地实现请求的发送并得到响应
      1. urlopen
        • urlopen()方法用法,官方文档:https://docs.python.org/3/library/urllib.request.html
        • urllib.request 模块提供最基本的构造HTTP请求的方法,利用他可以模拟浏览器的一个请求发起过程,同时还带有处理授权验证、重定向、浏览器Cookies以及其他内容
          import urllib.request
          response = urllib.request.urlopen('https://www.python.org')
          print(response.read().decode('utf-8'))
          print(type(response))
          
          输出类型为<class 'http.client.HTTPResponse'>
        • 以上内容是一个HTTPResponse类型对象,主要包含read()、readinto()、getheader(name)、getheaders()、fileno()等方法,以及msg、version、status、reason、debuglevel、close等属性。得到这个对象后,把它赋值为response变量,然后就可以调用这些方法属性,得到返回结果的一系列信息。
          • 例:调用read()方法可以德奥犯规的网页内容,调用status属性可以得到返回状态码(200代表请求成功,404代表网页未找到等)
        • 如果想给链接传递一些参数,如何实现,urlopen()函数的API:
          • urllib.request.urlopen(url,data=None,[timeout,]*,cafile=None,capath=None,cadefault=False,context=None),    意味着除了可以传递URL之外,还可以传递其他内容,比如data(附加数据)、timeout(时间设置)等,
            • data参数
              • data参数是可选的,如果需要添加,并且如果它是字节流编码格式的内容,即bytes类型,则需要通过bytes()方法转化。另外,如果传递这个参数,则他的请求方式就不在是Get方式,而是POTS方式。例:
                import urllib.parse
                import urllib.request
                data = bytes(urllib.parse.urlencode({'word':'hello'}),encoding = 'utf-8')
                response = urllib.request.urlopen('http://httpbin.org/post',data= data)
                print(response.read())

                这里我们传递一个参数word,值是hello。需要被转码成bytes(字节流)类型。其中转字节流采用了bytes()方法,该方法的第一个参数需要是str(字符串)类型,需要用urllib.parse模块里的urlencode()方法来将参数字典转化为字符串。  这里请求的站点是httpbin.org,他可以提供HTTP请求测试。本次的链接中http://httpbin.org/post可以用来测试POST请求,可以输出请求的一些信息,其中包含我们传递的data参数。  我们传递的参数出现在form字段中,是表明模拟表单提交方式,以POST方式传输数据

            • timeout参数
              • timeout参数用于设置超时时间,单位为秒。它支持HTTP,HTTPS,FTP请求
            • 其他参数
              • context参数,它必须是ssl.SSLContext类型,用来指定SSL设置
              • cafile和capath参数,分别指定CA证书和它的路径,这个在请求HTTPS链接时会有用
              • cadefault参数默认False,已弃用
      2. Request
        • 例:
          import urllib.request
          request = urllib.request.Request('https://python.org')
          response = urllib.request.urlopen(request)
          print(response.read().decode('utf-8'))

          依然使用urlopen()方法来发送请求,这次该方法的参数不再是URL,而是一个Request类型的对象。通过构造这个数据结构,一方面可以将请求独立成一个对象,另一方面可以更加丰富和灵活配置参数。

          • Request的参数构造
            • class urllib.request.Request(url, data=None, headers={}, origin_req_host=None,unverifiable=False,methond=None)    
              • url参数:用于请求URL,这是必传参数,其他都是可选参数。    
              • data参数:可选传参数,如果传必须要是bytes(字节流)类型的。如果它是字典,可以先用urllib.parse模块里的urlencode()编码。    
              • headers参数:是一个字典,它就是请求头,可以在构造请求时通过headers参数直接构造,也可以通过调用请求实例的add_header()方法添加。  添加最常用的方法就是修改User-Agent来伪装浏览器,默认的User-agent是Python-urllib,可以通过修改它来伪装。      
                • 例:伪装火狐浏览器,可设置为: Mozilla/5.0(X11; u; Linux i686) Gecko/20071127 Firefox/2.0.0.11    
              • origin_req_host参数:请求方的host名称或者IP地址    
              • unverifiable参数:表示这个请求是否无法验证的,默认是False。意思就是说用户没有足够权限来选择接受这个请求的结果    
                • 例:请求一个HTML文档中的图片,但是没有自动抓取图像的权限,这时候unverifiable的值就是True。    
              • method参数:是一个字符串,用来指示请求使用的方法,如GET、POST和PUT等。    
            • 传入多个参数构建请求:    
              from urllib import request,parse
              url='https://httpbin.org/post'
              headers={'User-Agent':'Mozilla/4.0(com[atible;MSIE 5.5; Windows NT)',
                              'host':'httpbin.org'}
              dict={'name':'Germey'}
              data = bytes(parse.urlencode(dict),encoding='utf-8')
              req=request.Request(url=url,data=data,headers= headers,method = 'POST')
              response=request.urlopen(req)
              print(response.read().decode('utf-8'))    

              其中headers指定User-Agent和Host,参数data用urlencode()和bytes()方法转成字节流。另外指定请求方式为POST。

            • 另外headers也可以用add_header()方法来添加:
              req= request.Request(url=url,datd= data, method='POST)
              req.add_headers('User-Agent','Mozilla/4.0(compatible; MSIE 5.5; Windows NT)')
      • 3.高级用法
        • urllilb.request模块里的BaseHandler类,是所有其他Handler的父类,它提供最基本的方法,例:default_open()、protocol_request等(各种Handler子类继承BaseHandler类举例)
          • HTTPDefaultErrorHandler:用于处理HTTP响应错误,错误都会抛出HTTPError类型的异常。
          • HTTPRedirectHandler:用于处理重定向
          • HTTPCookieProcessor:用于处理Cookies
          • ProxyHandler:用于设置代理,默认代理为空
          • HTTPPasswordMgr:用于管理密码,它维护了用户名和密码的表
          • HTTPBasicAuthHandler:用于管理认证,如果一个连接打开时需要认证,那么可以用它来解决认证问题
          • 其他Handler类,参考官方文档:https://docs.python.org/3/library/urllib.request.html#urllib.request.BaseHandler
        • 另一个比较重要的类OPenerDirector,可以称为Opener。Opener可以使用open()方法,返回类型和urlopen()如出一辙。就是利用Handler来构建Opener().
          • 验证
            • 部分网站打开需要你输入用户名和密码,验证成功才能查看页面,如果请求这样页面。借助HTTPBasicAuthHandler可以完成,相关代码:
              from urllib.request import HTTPPasswordMgrWithDefaultRealm,HTTPBasicAuthHandler,build_opener
              from urllib.error import URLError
              username = 'username'
              password='password'
              url='http://localhost:5000/'
              p=HTTPPasswordMgrWithDefaultRealm()
              p.add_password(None,url,username,password)
              auth_handler=HTTPBasicAuthHandler(p)
              opener=build_opener(auth_handler)
              try:
                  result=opener.open(url)
                  html=result.read().decode('utf-8')
                  print(html)
              except URLError as e:
                  print(e.reason)

              首先实例化HTTPBasicAuthHandler对象,其参数HTTPPasswordMgrWithDefaultRealm对象,它利用add_password()添加进去用户名和密码,这样就能建立一个处理验证的Handler。  接下来,利用这个Handler并使用build_opener()方法构建一个Opener,这个Opener在发送请求时就相当于已经验证成功。  利用Opener()的open方法打开链接就可以完成验证。  这里获取到的结果就是样后的页面码源内容。

          • 代理
            • 爬虫时候丢需要代理,如果要添加代理,可如下:
              from urllib.request import ProxyHandler,build_opener
              from urllib.error import URLError
              proxy_handler=ProxyHandler({
                  'http':'http://127.0.0.1:9743',
                  'https':'https://127.0.0.1:9743'
              })
              opener=build_opener(proxy_handler)
              try:
                  response =opener.open('https://www.baidu.com')
                  print(response.read().decode('utf-8'))
              except URLError as e:
                  print(e.reason)

              在本地搭建一个代理,运行在9734端口上。     这里是有ProxyHandler,其参数是一个字典,键名是协议类型(例:HTTP或HTTPS等),键值是代理链接,可添加多个代理。     然后利用这个Handler及build_opener()方法构造一个Opener,之后发送请求即可。

          • Cookies
            • Cookie的处理需要相关的Handler,例:
              import http.cookiejar,urllib.request
              cookie =http.cookiejar.CookieJar()
              handler =urllib.request.HTTPCookieProcessor(cookie)
              opener = urllib.request.build_opener(handler)
              response =opener.open('http://www.baidu.com')
              for item in cookie:
                  print(item.name+"="+item.value)

              首先,我们必须声明一个cookieJar对象。接下来,需要利用HTTPCookieProcessor构建一个Handler,再利用build_opener()方法构建出Opener,执行open()函数即可。这里会输出每条cookie的名称和值

            • Cookies实际上也是以文本形式保存的,既然能输出,也就可以输出成文件格式。例:
              import http.cookiejar,urllib.request
              filename='cookies.txt'
              cookie= http.cookiejar.MozillaCookieJar(filename)
              handler=urllib.request.HTTPCookieProcessor(cookie)
              opener=urllib.request.build_opener(handler)
              response=opener.open('http://www.baidu.com')
              cookie.save(ignore_discard=True,ignore_expires= True)

              这时CookieJar就需要换成MozillaCookieJar,它在生成文件时会用到,是CookieJar的子类,可以用来处理Cookies和文件相关的事件,比如读取和保存Cookies,可以将Cookies保存成Mozilla型浏览器的Cookies格式。运行后可以生成一个cookies.txt文件。           另外LWPcookieJar同样可以读取和保存Cookies,但是保存的格式和MozillaCookieJar不一样,会保存成libwww-perl(LWP)格式的cookies文件。可在声明时改为:

              cookie = http.cookiejar.LWPCookieJar(filename)
            • 读取cookie(LWPCookieJar格式为例):
              import http.cookiejar,urllib.request
              cookie= http.cookiejar.LWPCookieJar()
              cookie.load('cookies.txt',ignore_discard=True,ignore_expires=True)
              handler=urllib.request.HTTPCookieProcessor(cookie)
              opener=urllib.request.build_opener(handler)
              response=opener.open('http://www.baidu.com')
              print(response.read().decode('utf-8'))

              在此处调用load()方法来读取本地的Cookies文件,获取到Cookies内容。前提我们生成LWPCookieJar格式的Cookies文件。然后读取Cookies之后使用同一的方法构建Handler和Opener。   运行结果正常会输出百度页面的源代码。

            • urllib库的request模块基本使用方法,更多可参考官方文档:https://docs.python.org/3/library/urllib.request.html#basehandler-objects。
    • 2.处理异常  
      • urllib的error模块定义了由request模块产生的异常,如果出现问题,request模块便会抛出error模块中定义的异常
      1. URLError
        • URLError类来自urllib库的error模块,它继承自OSError类,是error异常模块的基类,由request模块生成的异常都可以通过捕捉这个类来处理。     它具有一个reason,即返回错误的原因。例:
          from urllib import request,error
          try:
              response=request.urlopen('https://cuiqingcai.com/index.htm')
          except error.URLError as e:    
              print(e.reason)

          打开不存在的页面,这时候捕获URLError这个异常,而没有报错。这样可以避免程序异常终止,同时异常得到有效处理。

      2. HTTPError
        • 它是URLError的子类,专门处理HTTP请求错误,如认证失败等。有如下3个属性
          1. Code:返回HTTP状态码,如404表示网页不存在,500表示服务器内部错误等。
          2. Reason:同父类一样,用于返回错误原因。
          3. headers:返回请求头。
        • 例:
          from urllib import request,error
          try:
              response=request.urlopen('https://cuiqingcai.com/index.htm')
          except error.HTTPError as e:    
              print(e.reason,e.code,e.heanders)

          同样的网址,这里捕获的是HTTPError异常,输出reason、code和headers属性。                                                                                                                                                      因为URLError是HTTP的父类,所以这里先选择捕获子类错误,再捕获父类错误,故代码更新如下:

          from urllib import request,error
          try:
              response=request.urlopen('https://cuiqingcai.com/index.htm')
          except error.HTTPError as e:    
              print(e.reason,e.code,e.heanders)
          except error.URLError as e:    
              print(e.reason)
          elseprint('Request Successfully')

          这样就可以先捕获HTTPError,获取它错误状态码、原因、headers等信息。如果不是HTTPError异常,就会捕获URLError异常,输出错误原因。                                                    有时候,reason属性返回的不一定是字符串,也可能是一个对象。例:

          import socket
          import urllib.request
          import urllib.error
          try:
              response= urllib.request.urlopen('https://www.baidu.com',timeout=0.01)
          except urllib.error.URLError as e:
              print(type(e.reason))
              if isinstance(e.reason,socket.timeout):
                  print('TIME OUT')

          这里直接设置超时来强制抛出timeout异常。reason属性的结果是socket.timeout类。所以,这可以用isinstance()方法来判断它的类,做出更详细的异常判断。

    • 3.解析链接
      1. urlparse()     
        • 该方法可以实现URL的识别和分段,例:
          from urllib.parse import urlparse
          result= urlparse('http://www.baidu.com/index.html;user?id=5#comment')
          print(type(result),result)

          这里利用urlparse()方法进行一个URL解析。返回结果是一个ParseResult类型对象,包含6个部分,分别是scheme、netloc、path、params、query和fragment。                            该实例的URL:  http://www.baidu.com/index.html;user?id=5#comment                                                                                                                                                                           urlparse()方法将其拆分成6个部分。解析有特定的分割符。    :// 前面的就是scheme,代表协议; 第一个/符号前面便是netloc,即域名,后面是path,即访问路径;分号;前面是params,代表参数;问好?后面是查询条件query,一般用作GET类型的URL;#后面是锚点,用于直接定位页面内部的下拉位置。                                                                         可以得出一个标准的链接格式:scheme://netloc/path;params?query#fragment                                                                                                                                                            一个标准的URL都会符合这个规则,利用urlparse()方法可以将它拆分开。

          • urlparse的API用法:urllib.parse.urlparse(urlstring,scheme=' ',allow_fragments=True),   ,它有3个参数。
            • urlstring:必填项,待解析的URL
            • scheme:默认协议(如http或https等)。假设这个链接没有带协议信息,会将这个作为默认协议。例:
              from urllib.parse import urlparse
              result = urlparse('www.baidu.com/index.html;user?id=5#comment',scheme='https')

              运行结果:ParseResult(scheme='https', netloc='', path='www.baidu.com/index.html', params='user', query='id=5', fragment='comment'),所提供的URL没有包含前面的scheme信息,但通过制定默认的scheme参数,返回结果是https。                                                                                                                                                    如果带上scheme:   result = urlparse('http://www.baidu.com/index.html;user?id=5#comment',scheme='https'), 结果就会返回解析出scheme带有的信息http

            • allow_fragments:即是否忽略fragment。如果被设置为False,fragment部分就会被忽略,他会被解析为path、parameters或者query的一部分,而fragment部分为空。例:
              from urllib.parse import urllib
              result =urlparse('http://www.baidu.com/index.html;user?id=5#comment',allow_fragments=False)
              print(result)

              运行结果:

              ParseResult(scheme='http', netloc='www.baidu.com', path='/index.html', params='user', query='id=5#comment', fragment='')

              如果URL中不包含params和query,例:

              from urllib.parse import urlparse
              result =urlparse('http://www.baidu.com/index.html#comment',allow_fragments=False)
              print(result)

              运行结果:

              ParseResult(scheme='http', netloc='www.baidu.com', path='/index.html#comment', params='', query='', fragment='')

              可发现,当URL中不包含params和query时,fragment便会被解析为path的一部分。                                                                                                                               返回结果ParseResult实际上是一个元组,可以通过索引顺序来获取,也可以用属性名获取。例:

              from urllib.parse import urlparse
              result = urlparse('http://www.baidu.com/index.html#comment',allow_fragments=False)
              print(result.scheme,result[0],result.netloc,result[1])

              这里,分别用了索引和属性名获取scheme和netloc。输出结果与一致:

              http
              http 
              www.baidu.com
              www.baidu.com
      2. urlunparse()

        • 有了urlparse()就会有对立方法urlunparse()。它接受的参数是一个可迭代对象,但长度必须是6,否则会抛出参数数量不足或过多问题.例:

          from urllib.parse import urlunparse
          data = ['http','www.baidu.com','index.html','user','a=6','comment']
          print(urlunparse(data))
          
          输出结果:
          http://www.baidu.com/index.html;user?a=6#comment

          这里参数使data使用列表类型,也可以使用其他类型,比如元组或特定的数据结构。这样就可以成功是此案URL构造

      3. urlsplit()

        • 此方法和urlparse()相似。例:

          from urllib.parse import urlsplit
          result=urlsplit('http://wwww.baidu.com/index.html;user?id=5#comment')
              print(result)
          运行结果:
          SplitResult(scheme='http', netloc='wwww.baidu.com', path='/index.html;user', query='id=5', fragment='comment')

          运行结果是SplitResult,也是一个元组类型,既可以用属性获取,也可以使用索引获取。例:

          from urllib.parse import urlsplit
          result=urlsplit('http://wwww.baidu.com/index.html;user?id=5#comment')
          print(result.scheme,result[0])
          输出:
          http http
      4. 与urlunsplit()
        • 与urlunparse类似,它也是讲链接各个部分组合完成完整链接的方法,传入的参数也是一个可迭代对象,例如列表、元组,唯一的区别是长度必须为5。示例如下:
          from urllib.parse import urlunsplit
          data =['http','www.baidu.com','index.html','a=6','comment']
          print(urlunsplit(data))
          输出:
          http://www.baidu.com/index.html?a=6#comment
      5. urljoin()

        •  有urlunparse()和urlunsplit()方法,就可以完成链接的合并,前提必须要有特定长度对象,链接的每一部分都要清晰分开。                                                                                        此外,生成链接还有另外一种方法,urljoin()方法。可以提供一个base_url(基础链接)作为第一个参数,将新的链接作为第二参数,该方法会分析base_url的scheme、netloc和path这3个内容并对新链接缺失的部分进行补充,最后返回结果。例:

          from urllib.parse import urljoin
          print(urljoin('http://www.baidu.com','FAQ.html'))
          >>>http://www.baidu.com/FAQ.html
          print(urljoin('http://www.baidu.com','https://cuiqingcai.com/FAQ.html'))
          >>> https://cuiqingcai.com/FAQ.html
          print(urljoin('http://www.baidu.com/about.html','https://cuiqingcai.com/FAQ.html?question=2'))
          >>> https://cuiqingcai.com/FAQ.html?question=2
          print(urljoin('http://www.baidu.com?wd=abc','https://cuiqingcai.com/index.php'))
          >>> https://cuiqingcai.com/index.php

          base_url提供三项内容scheme、netloc和path。如果这3项在新链接里不存在,就予以补充;如果新链接存在,就是要新链接部分。而base_url中的parms、query和fragment是不起作用的。

        • 通过urljoin()方法,可以轻松实现链接的解析、拼合与是生成。

      6. urlencode()

        • urlencode()在构造GET请求参数的时候非常有用,例:

          from urllib.parse import urlencode
          params = { 'name':'germey','age':22}
          base_url ='http:www.baidu.com?'
          url=base_url+urlencode(params)
          print(url)
          输出:
          http:www.baidu.com?name
          =germey&age=22

          这里首先声明一个字典来将参数表示出来,然后调用urlencode()方法将其序列化为GET请求参数。     可得到,参数成功滴有字典类型转化为GET请求参数了。

      7. parse_qs()

        • 反序列化。如果有一串GET请求参数,利用parse_qs()方法,就可以将它转回字典。例:

          from urllib.parse import parse_qs
          query = 'name=germey&age=22'
          print(parse_qs(query))
          输出:
          {'name': ['germey'], 'age': ['22']}

          这样就可以成功转回为字典类型了。

      8. parse_qsl()
        • parse_qsl()方法用于将参数转化为元组组成的列表,例:
          from urllib.parse import parse_qsl
          query ='name=germey&age=22'
          print(parse_qsl(query))
          输出:
          [('name', 'germey'), ('age', '22')]

          运行结果是一个列表,而列表中的每个元素都是一个元组,元组的第一个内容是参数名,第二个内容是参数值。

      9. quote()
        • 该方法可以将内容转化为URL编码格式。URL中带有中文参数时,有时候会导致乱码问题,此时用quote()这个方法可以将中文字符转化为URL编码。例:
          from urllib.parse import quote
          keyword='壁纸'
          url= 'https://www.baidu.com/s?wd='+quote(keyword)
          print(url)
          输出:
          https://www.baidu.com/s?wd=%E5%A3%81%E7%BA%B8

          这里声明一个中文的搜索文字,然后用quote()方法对其进行URL编码。

      10. unquote()
        • unquote()方法可以进行URL解码。例:
          from urllib.parse import unquote
          url='https://www.baidu.com/s?wd=%E5%A3%81%E7%BA%B8'
          print(unquote(url))
          输出:
          https://www.baidu.com/s?wd=壁纸

          上面得到URL编码后的结果,可以利用unquote()方法进行还原,解码。

    • 4.分析Robots协议
      • 利用urllib的robotparser模块,可以实现网站Robots协议的分析
      1. Robots协议
        • Robots协议也叫爬虫协议、机器人协议,用来告诉爬虫和搜索引擎哪些页面可以爬取。通常是一个叫robots。txt的文本文件,存放在网站的根目录下。                            当爬虫访问一个站点的时,会先检查这个站点根目录下是否穿在robots.txt文件,如果存在,爬虫会根据其中定义的范围来爬取。如果没找到这个文件,搜索爬虫变回访问所以可直接访问页面。  例一个robots.txt样例:
          User-agent:*
          Disallow:/
          Allow:/public/

          这实现对所有爬虫只允许爬取public目录功能,将上诉内容保存成robots.txt文件,放在网站的根目录下,和网站的入口文件(如index.php、index.html和index.jsp)放一起。

          • 上面的User-agent描述搜索爬虫的名称,这里将其设置为*代表协议对任何爬取爬虫都有效。例,可设置:                                                                                                        User-agent:Baiduspider                                                                                                                                                                                                                                    这就代表我们设置的规则对百度爬虫是有效的。如果头多条User-agent记录,则代表会有多个爬虫收到爬取限制,但至少需要指定一条。
          • Disallow指定不允许抓取的目录,比如上例子中设置为/则代表不允许抓取所有页面。
          • Allow一般和Disallow一起使用,一般不会单独使用,用来排除某些限制。现在设置为/public/,则表示所有页面不允许爬取,但可以抓取public目录。
            • 禁止所有爬虫访问任何目录的代码如下:
              User-agent
              Disallow:/

              允许所有爬虫访问任何目录的代码如下

              User-agent:*
              Disallow:

              另外把robots.txt文件留空也是可以的。

            • 禁止所有爬虫访问网站某些目录的代码:

              User-agent:*
              Disallow:/private/
              Disallow:/tmp/

              只允许某一个爬虫访问的代码如下:

              User-agent:WebCrawler
              Disallow:
              User-agent:*
              Disallow:/
      2. 爬虫名称
      3. Robotparser
        • 在了解robots协议后,就可以使用robotparser模块来解析robots.txt.该模块提供一个类RobotFileParser,它可以根据网站的robots.txt文件来判断一个爬取爬虫是否有权限来爬取这个网页。
        • 该类使用起来只需要在构造方法里传入robots.txt的链接即可。它的声明:urllib.robotparser.robotFileParser(url=' ')
        • 当然,也可以在声明时不传入,默认为空,最后再使用set_url()方法设置一下也可。下面列出这个类常用几个方法:
          • set_url():用来设置robots.txt文件的链接。如果创建RobotFileParser对象时传入了链接,那么就不需要再使用这个方法链接
          • read():读取robots.txt文件并进行分析。注意:这个方法执行一个读取和分析操作,如果不调用这个方法,接下来判断都为False,所以一定需要调用。它不会返回任何内容,但执行读取操作。
          • parse():用来解析robots.txt文件,传入的参数是robots.txt某些行的内容,他会按照robots.txt的语法规则来分析这些内容
          • can_fetch():该方法传入两个参数,第一个是User-agent,第二个是要抓取的URL。返回的内容是该搜索引擎是否可以抓取这个URL,返回结果是True或Flase
          • mtime():返回的是上次抓取和分析robots.txt的时间,这对于长时间分析和抓取的搜索爬虫是很有必要,需要定期检查来抓取最新的robots.txt  
          • modified():它同样对长时间分析和抓取的搜索爬虫有帮助,将当前时间设置为上次抓取和分析robots.txt的时间
            • 例:
              from urllib.robotparser import RobotFileParser
              rp = RobotFileParser()
              rp.set_url('http://www.jianshu.com/robots.txt')
              rp.read()
              print(rp.can_fetch('*','http://www.jianshu.com/p/b67554025d7d'))
              print(rp.can_fetch('*','http://www.jianshu.com/search?q=python&page=1&type=collections'))

              这里以简书为例,首先创建RobotFileParser对象,然后通过set_url()方法设置robots.txt的链接。或者可以在声明时候直接用:rp=RobotFileParser('http://www.jianshu.com/robots.txt'), 接着使用can_fetch()方法判断网页是否被抓取。

            • 同样也可以使用parse()方法执行读取和分析,示例:
              from  urllib.robotparser  import RobotFileParser
              from  urllib.request  import urlopen
              rp  =  RobotFileParser()
              rp.parse(urlopen('http://www.jianshu.com/robots.txt').read().decode('utf-8').split('\n'))
              print(rp.can_fetch('*','http://www.jianshu.com/p/b6755402Sd7d'))
              print(rp.can_fetch('*',"http://www.jianshu.com/search?q=python&page=l&type=collections"))

               

  2. 使用requests
    1. 基本用法
      1. 实例
        • urllib库中的urlopen()方法实际上是以GET方式请求网页,而requests中相应的方法就是get()方法。例:
          import requests
          r=requests.get('https://www.baidu.com/')
          print(type(r))
          >>> <class 'requests.models.Response'>
          print(r.status_code)
          >>> 200
          print(type(r.text))
          >>> <class 'str'>
          print(r.text)
           >>>    <!DOCTYPE html>
                        .......
                       </html>
          print(r.cookies)
          >>> <RequestsCookieJar[<Cookie BDORZ=27315 for .baidu.com/>]>

          这里调用get()方法实现与urlopen()相同操作,得到一个Response对象,然后分别输出Response的类型,状态码、响应体的类型、内容以及cookies。  

        • 它的返回类型是requests.models.Response,响应体的类型是字符串str,Cookies的类型是RequestCookieJar。
        • 其他的请求类型依然可以使用一句话来完成,例:
        • r = requests.post('http://httpbin.org/post')
          r = requests.put('http://httpbin.org/put')
          r = requests.delete('http://httpbin.org/delete')
          r = requests.head('http://httpbin.org/get')
          r = requests.options('http://httpbin.org/get')

          这里分别用post()、put()、delete()等方法实现POST、PUT、DELETE等请求

      2. GET请求
        • HTTP中最常见的请求之一就是GET请求
        1. 构建GET请求
          • 请求链接为http://httpbin.org/get,该网站会判断如果客户端发起的是GET请求的话,返回响应请求信息:
            import requests
            r= requests.get('http://httpbin.org/get')
            print(r.text)
            输出结果:
            {
              "args": {},
              "headers": {
                "Accept": "*/*",
                "Accept-Encoding": "gzip, deflate",
                "Connection": "close",
                "Host": "httpbin.org",
                "User-Agent": "python-requests/2.18.4"
              },
              "origin": "180.165.241.94",
              "url": "http://httpbin.org/get"
            }

            返回结果中包含请求头,URL、IP等信息

          • 如果对于GET请求需要附加额外信息,例(现在要添加两个参数,其中name是germey,age是22。要构造这样请求链接)
            • 一般这种信息数据会用字典来存储,构造链接可使用params参数:
              import rrequests
              data = {
                      'name': 'germey',
                      'age': 22
              }     
              r = requests.get("http://httpbin.org/get",params=data)
              print(r.text)
              输出结果:
              {
                "args": {
                  "age": "22",
                  "name": "germey"
                },
                "headers": {
                  "Accept": "*/*",
                  "Accept-Encoding": "gzip, deflate",
                  "Connection": "close",
                  "Host": "httpbin.org",
                  "User-Agent": "python-requests/2.18.4"
                },
                "origin": "180.165.241.94",
                "url": "http://httpbin.org/get?name=germey&age=22"
              }

              链接被自动构造成:http://httpbin.org/get?age=22&name=germey。

            • 网页的返回类型实际上是str类型,但特殊的是JSON格式的。如果想直接解析返回结果,得到一个字典格式,可以直接调用json()方法。例:

              import requests
              r= requests.get('http://httpbin.org/get')
              print(type(r.text))
              >>>  <class 'str'>
              print(r.json())
              >>>  {'args': {}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 
              'Connection': 'close', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.18.4'},
              'origin': '180.165.241.94', 'url': 'http://httpbin.org/get'} print(type(r.json())) >>> <class 'dict'>

              调用 json ()方法,就可以将返回结果是 JSON 格式的字符串转化为字典。 但需要注意的书,如果返回结果不是 JSON 式,便会出现解析错误,抛出 json.dec er. JSQ\JOecodeError 异常

        2. 抓取网页
          • 上面的请求链接返回的是 JSON 形式的字符串, 如果请求普通的网页,则肯定能获得相应的 内容了。以“知乎”→“发现”页面为例:
            import requests
            import re
            headers ={
                'User-agent':'Mozilla/5.0(Macintosh;Intel Mac OS X 10_11_4)Applewebkit/537.36(KHTML,like Gecko)'
                             'Chrome/52.0.2743.116 Safari/537.36'
            }
            r= requests.get('https://www.zhihu.com/explore',headers=headers)
            pattern = re.compile('explore-feed.*?question_link.*?>(.*?)</a>',re.S)
            titles=re.findall(pattern,r.text)
            print(titles)

            这里我们加入了 headers 信息,其中包含了 User-Agent 字段信息, 也就是浏览器标识信息。 如果 不加这个,知乎会禁止抓取。 接下来我们用到了最基础的正则表达式来匹配出所有的问题内容

        3. 抓取二进制数据
          • 在以上例子中,抓取的是知乎的一个页面, 实际上它返回的是一个 HTML 文档。

          • 图片、音频、视频这些文件本质上都是由二进制码组成的,由于有特定的保存格式和对应的解析方式, 我们才可以看到这些形形色色的多媒体。 所以,想要抓取它们,就要拿到它们的二进制码。 下面以 GitHub 的站点图标为例 :

            import requests
            r=requests.get('https://github.com/favicon.ico')
            print(r.text)
            print(r.content)

            这里抓取的内容是站点图标,也就是在浏览器每一个标签上显示的小图标,这里打印了 Response 对象的两个属性,一个是 text,另一个是 content

             结果显示,r.text 出现了乱码,后者结果前稍有一个 b,这代表是 bytes 类型的数据。 由于图片是二进制数据,所以前者在打印时转化为 str 类型,也就是图片直接转化为字符串,这理所当然会出现乱码。

          •  将刚才提取到的图片保存下来 :

            import requests
            r=requests.get('https://github.com/favicon.ico')
            with open('favicon.ico','wb') as f:
                f.write(r.content)

            这里用了 open ()方法,第一个参数是文件名称,第二个参数代表以二进制写的形式打开,可 以向文件里写入二进制数据。 运行结束之后,可以发现在文件夹中出现了名为 favicon.ico 的图标.

          • 同样地,音频和视频文件也可以用这种方法获取
        4. 添加headers
          • 与 urllib.request 一样,我们也可以通过 headers 参数来传递头信息。 比如,在“知乎”的例子中,如果不传递 headers ,就不能正常请求:

            import requests
            r= requests.get('https://www.zhihu.com/explore')
            print(r.text)
            输出:
            <html>
            <head><title>400 Bad Request</title></head>
            <body bgcolor="white">
            <center><h1>400 Bad Request</h1></center>
            <hr><center>openresty</center>
            </body>
            </html>

            如果加上 headers 并加上 User-Agent 信息,那就没问题了。当然,我们可以在 headers 这个参数中任意添加其他的字段信息。

      3. POST请求

        1. 了解了最基本的 GET请求另外一种比较常见的请求方式是POST。 使用 requests实现POST请求示例如下:

                  这里还是请求 http://httpbin.org/post,该网站可以判断如果请求是 POST 方式,就把相关请求信息 返回。

          import requests
          data = {'name':'germey','age':22}
          r= requests.post('https://httpbin.org/post',data=data)
          print(r.text)
          输出:
          {
            "args": {}, 
            "data": "", 
            "files": {}, 
            "form": {
              "age": "22", 
              "name": "germey"
            }, 
            "headers": {
              "Accept": "*/*", 
              "Accept-Encoding": "gzip, deflate", 
              "Connection": "close", 
              "Content-Length": "18", 
              "Content-Type": "application/x-www-form-urlencoded", 
              "Host": "httpbin.org", 
              "User-Agent": "python-requests/2.18.4"
            }, 
            "json": null, 
            "origin": "222.72.135.177", 
            "url": "https://httpbin.org/post"
          }

          获得了返回结果,其中 form 部分就是提交的数据,这就证明 POST请求成功发送了。

      4. 响应

        1. 在上面的实例中,使用 text 和 content 获取了响应的内容。 此外,还有很多属性和方法可以用来获取其他信息,如状态码、响应头、Cookies 等。 例:

          import requests
          r= requests.get('http://www.jianshu.com')
          print(type(r.status_code),r.status_code)
          print(type(r.headers),r.headers)
          print(type(r.cookies),r.cookies)
          print(type(r.url),r.url)
          print(type(r.history),r.history)
          输出:
          <class 'int'> 403
          <class 'requests.structures.CaseInsensitiveDict'> {'。。。。‘}
          <class 'requests.cookies.RequestsCookieJar'> <RequestsCookieJar[]>
          <class 'str'> https://www.jianshu.com/
          <class 'list'> [<Response [301]>]

          headers 和 cookies 这两个属性得到的结果分别是 CaselnsensitiveDict 和 RequestsCookieJar 类型。状态码常用来判断请求是否成功,而 requests 还提供了一个内置的状态码查询对象 requests.codes。

    2. 高级用法

      1. 文件上传

        • requests 可以模拟提交一些数据。 假如有的网站需要上传文件,我们也可以用它来实现,例:

          import requests
          files= {'file':open('favicon.ico','rb')}
          r=requests.post('http://httpbin.org/post',files = files)
          print(r.text)
          输出:
          {
            "args": {}, 
            "data": "", 
            "files": {
              "file": "data:application/octet-。。。。。。。
            }, 
            "form": {}, 
            "headers": {
              "Accept": "*/*", 
              "Accept-Encoding": "gzip, deflate", 
              "Connection": "close", 
              "Content-Length": "6665", 
              "Content-Type": "multipart/form-data; boundary=bd3497943aab4957a00081fa815f9fb2", 
              "Host": "httpbin.org", 
              "User-Agent": "python-requests/2.18.4"
            }, 
            "json": null, 
            "origin": "222.72.135.177", 
            "url": "http://httpbin.org/post"
          }

          在前一节中我们保存了一个文件 favicon.ico,这次用它来模拟文件上传的过程。 需要注意的是, favicon.ico 需要和当前脚本在同一 目录下。 

          网站会返回响应,里面包含 files 这个字段,而 form 字段是空的,这证明文件上传部分会单独有一个 files 字段来标识。

      2. cookies 

        • 实例获取cookies:

          import requests
          r=requests.get('https://www.baidu.com')
          print(r.cookies)
          for key,value in r.cookies.items():
              print(key+'='+value)
          输出:
          <RequestsCookieJar[<Cookie BDORZ=27315 for .baidu.com/>]>
          BDORZ=27315

          首先调用 Cookies 属性可成功得到 Cookies, 它是 RequestCookieJar 类型。 然后用 items()方法将其转化为元组组成的列表遍历输出每一个 Cookie 的名称和值, 实现 Cookie 的遍历解析

          也可以直接用 Cookie 来维持登录状态, 以知乎为例来说明。 首先登录知乎,将 Headers 中的 Cookie 内容复制下来(也可以替换成你自己的 Cookie, 将其设置到 Headers 里面,然后发送请求)例:

          import requests
          #cookies复制页面内容
          headers={
              'cookie':'tgw_l7_route=61066e97b5b7b3b0daad1bff47134a22; _zap=071c5b18-9263-4e48-94b4-6dd873eb96df; _xsrf=E3rYl2Ha3t7gHUZorSW4lNV5anb0pZ5q; d_c0="ALAgxXlQmw6PTneI9Sf4UudI5Aa_1gR6rWg=|1543721138"; capsion_ticket="2|1:0|10:1543721146|14:capsion_ticket|44:NDJmNWU1OTBjZmNmNDNmMmFlMzNhN2ZmYzI1OTlhNjQ=|98ede4acef94b134af08f731d6f8ffa86df1b050cfd62ca13561eedee40d6c89"; z_c0="2|1:0|10:1543721177|4:z_c0|92:Mi4xemtlakJnQUFBQUFBb0tHMWVWQ2JEaWNBQUFDRUFsVk4yZDBxWEFEZ3JBSEpBUjN4aDhhc2ctWnJUMDFJX04tQmJ3|12e6bb5be1674015891fd6d113432a80722aeeb90b2d596add445f1d9c106ff1"; tst=r; q_c1=370d2ccccb2f401a8fabe43e6c9e63ea|1543721178000|1543721178000',
              'Host':'www.zhihu.com',
              'User-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36'
          }
          r=requests.get('https://www.zhihu.com',headers=headers)
          print(r.text)

          结果中包含了登录后的结果,这证明登录成功

          当然,可以通过 cookies 参数来设置,不过这样就需要构造 RequestsCookieJar 对象,而且需要分割一下 cookies。 相对烦琐,不过效果是相同的,示例:

          import requests
          cookies='tgw_l7_route=61066e97b5b7b3b0daad1bff47134a22; _zap=071c5b18-9263-4e48-94b4-6dd873eb96df; _xsrf=E3rYl2Ha3t7gHUZorSW4lNV5anb0pZ5q; d_c0="ALAgxXlQmw6PTneI9Sf4UudI5Aa_1gR6rWg=|1543721138"; capsion_ticket="2|1:0|10:1543721146|14:capsion_ticket|44:NDJmNWU1OTBjZmNmNDNmMmFlMzNhN2ZmYzI1OTlhNjQ=|98ede4acef94b134af08f731d6f8ffa86df1b050cfd62ca13561eedee40d6c89"; z_c0="2|1:0|10:1543721177|4:z_c0|92:Mi4xemtlakJnQUFBQUFBb0tHMWVWQ2JEaWNBQUFDRUFsVk4yZDBxWEFEZ3JBSEpBUjN4aDhhc2ctWnJUMDFJX04tQmJ3|12e6bb5be1674015891fd6d113432a80722aeeb90b2d596add445f1d9c106ff1"; tst=r; q_c1=370d2ccccb2f401a8fabe43e6c9e63ea|1543721178000|1543721178000',
          jar =requests.cookies.RequestsCookieJar()
          headers={
              'Host':'www.zhihu.com',
              'User-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36'
          }
          for cookie in cookies:
              key,value =cookie.split('=',1)
              jar.set(key,value)
          r=requests.get('https://www.zhihu.com',cookies =jar,headers=headers)
          print(r.text)

          首先新建了一个 RequestCookieJar 对象,利用 set()方法设置好每个 Cookie 的 key 和 value,然后通过调用 requests 的 get()方法并传递给 cookies 参数。 由于知乎本身的限制, headers 参数也不能少,只不过不需要在原来 的 headers 参数里面设置 cookie 字段了。测试后,发现同样可以正常登录知乎。

      3. 会话维持
        • requests 中,如果直接利用 get()或 post()等方法的确可以做到模拟网页的请求,但是这实际 上是相当于不同的会话,也就是说相当于你用了两个浏览器打开了不同的页面。

          Session,我们可以方便地维护一个会话,而且不用担心 cookies 的问题,它会帮我们自动处理好。 示例:

          import requests
          S = requests.Session()
          S.get('http://httpbin.org/cookies/set/number/123456789')
          r=S.get('http://httpbin.org/cookies')
          print(r.text)
          输出:
          {
            "cookies": {
              "number": "123456789"
            }
          }

          请求了一个测试网址 http://httpbin.org/cookies/set/number/ 1234567 89。 请求这个网址时,可以设置一个 cookie,名称叫作 number,内容是123456789,随后又请求了 http://httpbin.org/cookies, 此网址可以获取当前的 Cookies。 利用 Session ,可以做到模拟同一个会话而不用担心 Cookies 的问题。 它通常用于模拟登录 成功之后再进行下一步的操作.

      4. SSL证书验证
        • requests 还提供了证书验证的功能。 当发送 HTTP 请求的时候,它会检查 SSL 证书,我们 可以使用 veri干y 参数控制是否检查此证书。 其实如果不加 verify 参数的话,默认是 True,会自动验证。(12306 的证书没有被官方 CA 机构信任,会出现证书验证错误的结果)。
        • 如果请求一个 HTTPS 站点,但是证书 验证错误的页面时,把 verify 参数设置为 False 即可。 相关代码如下:
          import requests
          response = requests.get('https://www.12306.cn',verify =False)
          print(response.status_code)

          会发现报了一个警告它建议我们给它指定证书。 我们可以通过设置忽略警告的方式来屏蔽这个警告:

          import requests
          from requests.packages import urllib3
          urllib3.disable_warnings()  #忽略警告
          response = requests.get('https://www.12306.cn',verify =False)
          print(response.status_code)

          或者通过捕获警告到日志的方式忽略警告:

          import logging
          import requests
          logging.captureWarnings(True)    #忽略警告
          response = requests.get('https://www.12306.cn',verify =False)
          print(response.status_code)
      5. 代理设置
        • 对于某些网站,在测试的时候请求几次, 能正常获取内容。 但是一旦开始大规模爬取,对于大规 模且频繁的请求,网站可能会弹出验证码,或者跳转到登录认证页面, 更甚者可能会直接封禁客户端 的 IP,为了防止这种情况发生,我们需要设置代理来解决这个问题

        • 这就需要用到 proxies 参数。 可以用这样的方式设置示例(换成自己的有效代理试验):

          import requests
          proxies = {
              'http':'http://10.10.1.10:3128',
              'https':'http://10.10.1.10:1080'
          }
          requests.get('https://www.taobao.com',proxies = proxies)

          若代理需要使用 HTTP Basic Auth,可以使用类似 http://user: password@host: port这样的语法来设 置代理,示例如下:

          import requests
          proxies = {
              'http':'http://user:password@10.10.1.10:3128/',
          }
          requests.get('https://www.taobao.com',proxies = proxies)

          除了基本的 HTTP 代理外, requests 还支持 SOCKS 协议的代理。
          首先,需要安装 socks 这个库:pip3 install  ’requests[socks]'
          然后就可以使用 SOCKS 协议代理了,示例如下:

          import requests
          proxies = {
              'http':'socks5://user:password@host:port',
              'https':'socks5//user:password@host:port'
          }
          requests.get('https://www.taobao.com',proxies = proxies)
      6. 超时设置  

        • 为了防止服务器不能及时响应,应该设置一个超时时间, 即超过了这个时间还没有得到响应,就会报错。需要用到 timeout参数。 这个时间的计算是发出请求到服务器返回响应的时间。 示例如下:

          import requests
          r = requests.get('http://www.taobao.com',timeout = 1)
          print(r.status_code)

          可以将超时时间设置为 1 秒,如果 1 秒内没有响应,那就抛出异常。实际上,请求分为两个阶段,即连接( connect )和读取( read )。上面设置的 timeout 将用作连接和读取这二者的 timeout 总和。
          如果要分别指定,就可以传入一个元组:

          r = requests.get('https://www.tabao.com', timeout=(5,11,30))

          如果想永久等待,可以直接将 timeout 设置为 None,或者不设置直接留空,因为默认是 None.

      7. 身份认证

        • 在访问网站时,我们可能会遇到这样的认证页面,此时可以使用 requests 自带的身份认证功能,示例:

          import requests
          from requests.auth import HTTPBasicAuth
          r= requests.get('http://localhost:5000',auth=('username','password'))
          print(r.status_code)

          如果用户名和密码正确的话,请求时就会自动认证成功,会返回 200 状态码;如果认证失败, 则 返回 401 状态码。

      8.  

        Prepared Request

        • 前面介绍 urllib 时,可以将请求表示为数据结构,其中各个参数都可以通过一个 Request 对 象来表示。 这在 requests 里同样可以做到,这个数据结构就叫 Prepared Request。 例:

          from requests import Request,Session
          url= 'http://httpbin.org/post'
          data = {
              'name':'germey'
          }
          headers={'User-agent':'Mozilla/5.0(Macintosh;Intel Mac OS X 10_11_4)Applewebkit/537.36(KHTML,like Gecko)'
                           'Chrome/52.0.2743.116 Safari/537.36'}
          s=Session()
          req=Request('POST',url,data=data, headers=headers)
          prepped= s.prepare_request(req)
          r= s.send(prepped)
          print(r.text)

          这里引入一个Request,然后使用url、data、和headers参数构造一个Request对象,这时需要再2调用Session的prepa_request()方法转换为一个Prepare Request对象,再调用send()方法发送即可。

          有了 Request 这个对象,就可以将请求当作独立的对象来看待,这样在进行队列调度时会非常方 便。 肩而我们会用它来构造一个 Request 队列。

  3. 正则表达式

    • 正则表达式是处理字符串的强大工具,它有向己特 定的语法结构,实现字符串的检索、替换、匹配验证等等

    1. 实例引入

      • 开源中国提供的正则表达式测试工具 http://tool.oschina.net/regex/,输入待匹配的文本, 然后 选择常用的正则表达式,就可以得出相应的匹配结果。

      • URL,开头是协议类型,然后是冒号加双斜线,最后是域名加路径。对于 URL来说,可以用下面的正则表达式匹配:
        [a-zA-z]+://(<\s]*
        用这个正则表达式去匹配一个字符串,如果这个字符串中包含类似 URL 的文本,那就会被提取出来。

        a-z 代表匹配任意的小写字母 \s 表示匹配任意的空白字符,*就代表匹配前面的字符任意多个,这一长 串的正则表达式就是这么多匹配规则的组合。

        Python 的 re 库提供了 整个正则表达式的实现,利用这个库,可以在 Python 中使用正则表达式。 在 Python 中写正则表达式 几乎都用这个库。

    2. match()

      1. match() 方法是从字符串的开头开始匹配的,一旦开头不匹配,那么整个匹配就失败
        • 一个常用的匹配方法——match(),向它传人要匹配的字符串以及正则表达式,就可以检测这个正则表达式是否匹配字符串。match()方法会尝试从字符串的起始位置匹配正则表达式,如果匹配,就返回匹配成功的结果;如果不匹配,就返回 None。 例:

          import re
          content = 'hello 123 4567 World_This is a Regex Demo'
          print(len(content))
          result = re.match('^hello\s\d\d\d\s\d{4}\s\w{10}',content)    #第一个参数传入正则表达式,第二个参数传入要匹配的字符串
          print(result.group())
          print(result.span())
          输出:
          41
          hello 123 4567 World_This
          (0, 25)
          这里首先声明了一个字符串,其中包含英文字母、空白字符、数字等。 接下来,我们写一个正则 表达式: ' Hello\s\d\d\d\s\d{4}\s\w{10}     用它来匹配这个长字符串。 开头的 ^ 是匹配字符串的开头,也就是以 Hello 开头;然后\s 匹配空白字符,用来匹配目标字符串的空格;\d 匹配数字, 3 个\d 匹配 123 ;然后再写 1 个\s 匹配空格; 后 面还有 4567,我们其实可以依然用 4个\d 来匹配,但是这么写比较烦琐, 所以后面可以跟{4}代表匹配前面的规则 4 次,也就是匹配 4个数字;然后后面再紧接 1个空白字符,最后\w{10}匹配10个字母及下划线。 我们注意到,这里其实并没有把目标字符串匹配完,不过这样依然可以进行匹配,只不 过匹配结果短一点而已。

          而在 match()方法中,第一个参数传入了正则表达式,第二个参数传入了要匹配的字符串。  

          group() 方法可以输出匹配到的内容,结果是 Hello 123 4567 World_This ,恰好是正则表达式规则所匹配的内容;span()方法可以输出匹配的范围,结果是(0,25),这就是匹配到的结果字符串在原字符串中的位置范围。 

      2. 匹配目标

        • 如果想从字符串中提取一部分内容, 可以使用()括号将想提取的子字符串括起来。 ()实际上标记了一个子表达式的开始和结束位 置,被标记的每个子表达式会依次对应每一个分组,调用 group()方法传入分组的索引即可获取提取 的结果。 示例:

          import re
          content = 'Hello 1234567 World_This is a Regex Demo'
          result = re.match('Hello\s(\d+)\sWorld', content)
          print(result) 
          print(result.group())
          print(result.group(1))
          print(result.span())
          输出:
          <_sre.SRE_Match object; span=(0, 19), match='Hello 1234567 World'>
          Hello 1234567 World
          1234567
          (0, 19)

          这里我们想把字符串中的 1234567 提取出来, 此时可以将数字部分的正则表达式用()括起来, 然 后调用了 group(l)获取匹配结果。

          结果得到了 1234567。 这里用的是 group(1)与 group()有所不同,后者会输出 完整的匹配结果,而前者会输出第一个被()包围的匹配结果。假如正则表达式后面还有()包括的内容, 那么可以依次用 group(2)、group(3)等来获取。

      3.  通用匹配

        • 万能匹配,那就是 .*(点 星)。 其中 .(点)可以匹配任意字符(除换行符),*(星)代表匹配前面的字符无限次,所以它们组合在一起就可以匹配任意字符了。例:

          import re
          content = 'Hello 1234567 World_This is a Regex Demo'
          result= re.match('Hello.*Demo$',content)
          print(result)
          print(result.group())
          print(result.span())
          输出:
          <_sre.SRE_Match object; span=(0, 40), match='Hello 1234567 World_This is a Regex Demo'>
          Hello 1234567 World_This is a Regex Demo
          (0, 40)

          这里我们将中间部分直接省略,全部用 *来代替,最后加一个结尾字符串就好。   可以看到, group ()方法输出了匹配的全部字符串,也就是说我们写的正则表达式匹配到了目标字 符串的全部内容; span ()方法输出(0 ,40),这是整个字符串的长度 因此,我们可以使用.*简化正则表达式的书写

      4. 贪婪与非贪婪

        1. 贪婪匹配
          • 在贪婪匹配下,*会匹配尽可能多的字符 正则表达式中.*后面是 \d+,也就是至少一个数字,并没有指定具体多少个数字。因此, 就尽可能匹配多的字符。例:
            import re
            content = 'Hello 1234567 World_This is a Regex Demo'
            result= re.match('He.*(\d+).*Demo$',content)
            print(result)
            print(result.group(1))
            输出:
            <_sre.SRE_Match object; span=(0, 40), match='Hello 1234567 World_This is a Regex Demo'>
            7

            这里就把 123456 匹配了,给\d+留下一个可满足条件的数字 7,最后得到的内容就只有数 字 7 了。 

        2. 非贪婪匹配
          • 非贪婪匹配的写法是.*?多了一个?。例:

            import re
            content = 'Hello 1234567 World_This is a Regex Demo'
            result = re.match('He.*?(\d+).*Demo$',content)
            print(result)
            print(result.group(1))
            输出:
            <_sre.SRE_Match object; span=(0, 40), match='Hello 1234567 World_This is a Regex Demo'>
            1234567

            这里只是将第一个 .* 改成了 .*?,转变为非贪婪匹配。贪婪匹配是尽可能匹配多的字符,非贪婪匹配 就是尽可能匹配少的字符。 当.*?匹配到 Hello 后面的空向字符时,再往后的字符就是数字了 ,而\d+ 恰好可以匹配,那么这里.*?就不再进行匹配,交给\d+去匹配后面的数字。 所以这样.*?匹配了尽可能少的字符,\d+的结果就是 1234567 了。

            在做匹配的时候,字符串中间尽量使用非贪婪匹配,也就是用 .*?来代替 .*,以免出现匹配结果缺失的情况。注意如果匹配的结果在字符串结尾,.*?就有可能匹配不到任何内容了,因为它会 匹配尽可能少的字符。  
      5. 修饰符
        • 正则表达式可以包含一些可选标志修饰符来控制匹配的模式。 修饰符被指定为一个可选的标志。 例(在字符串中加了换行符,正则表达式不变,来匹配的数字):
          import re
          content = '''Hello 1234567 World_This 
          is a Regex Demo'''
          result = re.match('He.*?(\d+).*?Demo$',content)
          print(result.group(1))
          输出:
          报错--NoneType

          因为.匹配的是除换行符之外的任意字符, 当遇到换行符时,.*?就不能匹配了,所以导致匹配失败。 这里只需加一个修饰符 re.S,即可修正。修改result:

          result = re.match('He.*?(\d+).*?Demo$',content,re.S)

          这个修饰符的作用是使.匹配包括换行符在内的所有字符。 re.S 在网页匹配中经常用到。 因为 HTML 节点经常会有换行,加上它,就可以匹配节点与 节点之间的换行了。    

          • 修饰符:
            • re.I: 使匹配对大小写不敏感
            • re.L:做本地化识别( locale-aware )匹配
            • re.M:多行匹配,影响^和$
            • re.S: 使.匹配包括换行在内的所有字符
            • re.U:根据 Unicode字符集解析字符。 这个标志影响\w、\W、\b 和\B
            • re.X 该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解
      6. 转义匹配
        • 遇到用于正则匹配模式的特殊字符时,在前面加反斜线转义一下即可 例如 . 就可以用\. 来匹 配
          import re
          content = '(百度)www.baidu.com'
          result= re.match('\(百度\)www\.baidu\.com',content)
          print(result)
          输出:
          <_sre.SRE_Match object; span=(0, 17), match='(百度)www.baidu.com'>
    3. search()
      • match ()方法是从字符串的开头开始匹配的,一旦开头不匹配,那么整个匹配就失败, search ()在匹配时会扫描整个字符串,然后返回第一个成功匹配的结果。例:
        import re
        content = 'Extra strings Hello 1234567 World_This is a Regex Demo Extra strings'
        result= re.search('Hello.*?(\d+).*?Demo',content)
        print(result)
        输出:
        <_sre.SRE_Match object; span=(14, 54), match='Hello 1234567 World_This is a Regex Demo'>

        如果将search修改为match,返回值将为None。

        • 有一段待匹配的 HTML 文本,接下来写几个正则表达式实例来实现相应信息的提取:

          html = '''<div id="songs-list">
          <h2 class="title"〉经典老歌</h2>
          <p class="introduction">
           经典老歌列表
          </p>
          <ul id="list" class="list-group">
          <li data-view="2">一路上有你</1i>
          <li data-view="7">
          <a href ="/2.mp3 singer="任贤齐">沧海一卢笑</a>
          </li>
          <li data-view="4" class="active">
          <a href ="/3.mp3 singer="齐泰"> 往事随风 </a>
          </li>
          <li data-view="6"><a href="/4.mp3" singer ="beyond">光辉岁月</a></li>
          <li data-view="5"><a href="/S.mp3" singer ="陈慧琳">记事本</a></li>
          <li data-view="5"> 
          <a href ="/6.mp3" singer ="邓丽君">但愿人长久</a>
          </li>
          </ul>
          </div>'''

          ul 节点里有许多 li节点,其中 li 节点中有包含a节点,有不包含a节点,a节点还有一些相应的属性一一超链接和歌手名。                                                                                            提取 class为active的 li 节点内部的超链接包含的歌手名和歌名,此时需要提取第三个 li 节点下 a节点的 singer 属性和文本。                                                                                      此时正则表达式可以以 li 开头,然后寻找一个标志符 active ,中间的部分可以用  .*? 来匹配 ,接下来提取 singer 这个属性值,所以还需要写入 singer ="(.*?)",这里需要提取的部分用小括号括 起来,以使用 group() 方法提取出来,它的两侧边界是双引号。还需要匹配a节点的文本,其中它左边界是 >,右边界是 <\a> 然后目标内容依然用(.*?)来匹配,所以最后的正则表达式就变成了:

          <li.*?active.*?singer="(.*?)">(.*?)</a>

          再调用 search ()方法,它会搜索整个 HTML 文本,找到符合正则表达式的第一个内容返回。另外,由于代码有换行,所以这里第 三个参数需要传人 re.S 整个匹配代码:

          result = re.search('<li.*?active.*?singer"(.*?)">(.*?)</a>'.html,re.S)
          if result:
              print(result.group(1),result.group(2))

          由于需要获取的歌手和歌名都已经用小括号包围,所以可以用 group ()方法获取。

          注意,在上面的两次匹配中, search ()方法的第三个参数都加了 re.S ,这使得 *?可以匹配换行, 所以含有换行的 li 节点被匹配到了

    4. findall()

      • findall()方法了 该方法会搜索整 个字符串,然后返回匹配正则表达式的所有内容。

      • 如果想获取所有 节点的超链接、歌手和歌名,可以将 search()方法换成 findall()方法。如果有返回结果的话,就是列表类型所以需要遍历一下来依次获取每组内容。例:

        results = re.findall('<li.*?href="(.*?)".*?singer="(.*?)">(.*?)</a>',html,re.S)
        print(results)
        for result in results:
            print(result)
            print(result[0],result[1])

        返回列表的每个元素都是元组类型,用对应索引依次取出。   如果只是获取第 个内容,可以用 search ()方法 当需要提取多个内容时,可以用于indall() 方法

    5. sub()
      • 想要把一串文本中的所有数字都去掉,如果只用字符串的replace()方法,那就太烦琐了,这时可以借助sub()方法。例:
        import re
        content = '54aKyr5oiR54ix5L2g'
        content = re.sub('\d+','',content)
        print(content)
        输出:
        aKyroiRixLg

        这里只需要给第一个参数传入\ +来匹配所有的数字,第二个参数为替换成的字符串(如果去掉 该参数的话,可以赋值为空),第 个参数是原字符串。                                                  上面的 HTML 文本中,如果想获取所有 li 节点的歌名,直接用正则表达式来提取可能比较繁琐,如果借助sub()方法。可以先用 sub()方法将a节点去掉,只留下文本,再利用 findall()提取就好了。例:

        html = re.sub('<a.*?>|</a>','',html)
        print(html)
        results = re.findall('<li.*?>(.*?)</li>',html,re.S)
        for result in results:
            print(result.strip())

        a节点经过 sub()方法处理后就没有了,然后再通过 findall()方法直接提取即可.

    6. compile()
      • compile()方法可以将正则字符串编译成正则表达式对象,以便在后面的匹配中复用。例:
        import re
        content1 = '2016-12-15 12:00'
        content2 ='2016-12-17 12:55'
        content3 ='2016-12-22 13:21'
        pattern = re.compile('\d{2}:\d{2}')
        result1=re.sub(pattern,'',content1)
        result2=re.sub(pattern,'',content2)
        result3=re.sub(pattern,'',content3)
        print(result1,result2,result3)
        输出:
        2016-12-15  2016-12-17  2016-12-22 

        有3个日期,分别将3个日期中的时间去掉,这时可以借助 sub()方法。该方法的第一个参数是正表达式,但是这里没有必要重复写 个同样的正则表达式,此时可以借助compile()方法将正则表达式编译成一个正则表达式对象,以便复用。                                                                                                                                                                                                       另外,compile()还可以传入修饰符,如 re 等修饰符,这样在search()、findall() 等方法中就不需要额外传了。所以,compile()方法可以说是给正则表达式做了一层封装,以便我们更好地复用。

  4. 抓取猫眼电影排行

    1. 抓取分析

      • 目标站点为 http: // aoyan.com/board/4,对网页进行解析,寻找规律

    2. 抓取首页

      • 首先抓取第一页的内容。 我们使用get_one_page()方法,并给它传入url参数。将抓取的页面结果返回,再通过main()方法调用。 初步代码实现:

         1 import requests
         2 
         3 def get_one_page(url):
         4     headers={
         5         'User-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36'
         6     }
         7     response = requests.get(url,headers = headers)
         8     if response.status_code ==200:
         9         return response.text
        10     return None
        11 
        12 def main():
        13     url = 'http://maoyan.com/board/4'
        14     html = get_one_page(url)
        15     print(html)
        16 
        17 if __name__ == '__main__':
        18     main()

        可初步获取源代码,再对源代码进行解析

    3. 正则提取
      • 在开发者模式下的 Network 监昕组件中查看源代码(在Network下的Doc选项下寻找查看原始请求得到码源)
          1. 一部电影信息对应的源代码是一个 dd 节点,用正则表达式来提取这里面的一些 电影信息。首先,提取排名信息。而它的排名信息是在 class为board-index的 i 节点内, 利用非贪婪匹配来提取 i 节点内的信息,正则表达式写为:<dd>.*?board-index.*?>(.*?)</i>
          2.  随后提取电影的图片。 后面有 a 节点,其内部有两个 img 节点。 第二个 img 节点的 data-src 属性是图片的链接。 这里提取第二个 img 节点的 data-src 属性,正则表达式改写下:<dd>.*?board-index.*?>(.*?)</i>.*?data-src="(.*?)"
          3. 再往后,提取电影的名称,它在后面的 p 节点内,class为name。所以,可以用 name 做一个标志位,然后进一步提取到其内 a 节点的正文内容,此时正则表达式改写:<dd>.*?board-index.*?>(.*?)</i>.*?data-src="(.*?)".*?name.*?a.*?>(.*?)</a>
          4. 再提取主演、 发布时间、评分等内容时,都是同样的原理。 最后,正则表达式写为:.......
          5. 这样一个正则表达式可以匹配一个电影的结果,里面匹配了 7个信息。接下来,通过调用 findall() 方法提取出所有的内容。 
          6. 再定义解析页面的方法 parse_one_page(),主要是通过正则表达式来从结果中提取 ,LH我们想要的内容,实现代码:
            def parse_one_page(html):
                pattern=re.compile(
                    '<dd>.*?board-index.*?>(.*?)</i>.*?data-src="(.*?)".*?name.*?a.*?>(.*?)</a>.*?star.*?>(.*?)</p>.*?releasetime.*?>(.*?)</p>.*?integer.*?>(.*?)</i>.*?fraction.*?>(.*?)</i>.*?</dd>',
                    re.S )
                items=re.findall(pattern,html)
                for item in items:
                    yield{'index':item[0],
                          'image':item[1],
                          'title':item[2].strip(),
                          'actor':item[3].strip()[3:] if len(item[3])>3 else '',
                          'time' :item[4].strip()[5:] if len(item[4])>5 else '',
                          'score':item[5].strip() +item[6].strip()
                          }
    4. 写入文件
      • 将提取的结果写入文件。这里通过 JSON 库的 dumps() 方法实现字典的序列化,并指定 ensure_ascii 参数为 False,这样可以保证输出结果是中文形式而不 是 Unicode 编码。
        def write_to_file(content):
            with open('result.txt','a',encoding='utf-8') as f:
                print(type(json.dumps(content)))
                f.write(json.dumps(content,ensure_ascii=False)+'\n')

        通过调用 write_to_ file()方法即可实现将字典写入到文本文件的过程,此处的 content 参数就是 一部电影的提取结果,是一个字典。

    5. 整合代码块

      1. import requests
        import re
        import json
        
        def get_one_page(url):
            headers={
                'User-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36'
            }
            response = requests.get(url,headers = headers)
            if response.status_code ==200:
                return response.text
            return None
        
        def parse_one_page(html):
            pattern=re.compile(
                '<dd>.*?board-index.*?>(.*?)</i>.*?data-src="(.*?)".*?name.*?a.*?>(.*?)</a>.*?star.*?>(.*?)</p>.*?releasetime.*?>(.*?)</p>.*?integer.*?>(.*?)</i>.*?fraction.*?>(.*?)</i>.*?</dd>',
                re.S )
            items=re.findall(pattern,html)
            for item in items:
                yield{'index':item[0],
                      'image':item[1],
                      'title':item[2].strip(),
                      'actor':item[3].strip()[3:] if len(item[3])>3 else '',
                      'time' :item[4].strip()[5:] if len(item[4])>5 else '',
                      'score':item[5].strip() +item[6].strip()
                      }
                
        def write_to_file(content):
            with open('result.txt','a',encoding='utf-8') as f:
                #print(type(json.dumps(content)))
                f.write(json.dumps(content,ensure_ascii=False)+'\n')
        
        def main():
            url = 'http://maoyan.com/board/4'
            html = get_one_page(url)
            for item in parse_one_page(html):
                write_to_file(item)
                
        if __name__ == '__main__':
            main()
    6. 分页爬取

      1. 因为需要抓取的是 TOP100电影,所以还需遍历一下,给这个链接传入offset 参数,实现其他 90 部电影的爬取, 此时添加调用:

        if __name__ =='__main__':
            for i in range(10):
                main(offset= i*10)

        还需要将 main()方法修改一下,接收一个offset值作为偏移量,然后构造 URL 进行爬取。

        def main(offset):
            url = 'http://maoyuan.com/board/4?offset='+str(offset)
            html = get_one_page(url)
            for item in parse_one_page(html):
                write_to_file(item)
    7. 完整代码块
      • import json
        import requests
        import re
        import time
        from requests.exceptions import RequestException
        
        def get_one_page(url):
            try:
                headers={
                'User-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36'
            }
                response = requests.get(url,headers = headers)
                if response.status_code ==200:
                    return response.text
                return None
            except RequestException:
                return None
        
        def parse_one_page(html):
            pattern=re.compile(
                '<dd>.*?board-index.*?>(.*?)</i>.*?data-src="(.*?)".*?name.*?a.*?>(.*?)</a>.*?star.*?>(.*?)</p>.*?releasetime.*?>(.*?)</p>.*?integer.*?>(.*?)</i>.*?fraction.*?>(.*?)</i>.*?</dd>',
                re.S )
            items=re.findall(pattern,html)
            for item in items:
                yield{'index':item[0],
                      'image':item[1],
                      'title':item[2].strip(),
                      'actor':item[3].strip()[3:] if len(item[3])>3 else '',
                      'time' :item[4].strip()[5:] if len(item[4])>5 else '',
                      'score':item[5].strip() +item[6].strip()
                      }
        
        def write_to_file(content):
            with open('result.txt','a',encoding='utf-8') as f:
                f.write(json.dumps(content,ensure_ascii=False)+'\n')
        
        def main(offset):
            url = 'http://maoyan.com/board/4?offset='+str(offset)
            html = get_one_page(url)
            for item in parse_one_page(html):
                print(item)
                write_to_file(item)
        
        if __name__ == '__main__':
            for i in range(10):
                main(offset=i*10)
                time.sleep(1)

        增加了一个延时等待为防止反爬虫。

posted @ 2018-12-04 17:29  独行头狼  阅读(685)  评论(0编辑  收藏  举报