Python爬虫

文章目录

  • 一、爬虫基础
    • 1.HTML基本原理
      • 1.1.URI和URL
      • 1.2.超文本
      • 1.3.HTTP和HTTPS
      • 1.4.HTTP请求过程
      • 1.5.请求
        • 1.5.1.请求方法
        • 1.5.2.请求的网址
        • 1.5.3.请求头
        • 1.5.4.请求体
      • 1.响应
        • 1.1.响应状态码
        • 1.2.响应头
        • 1.3.响应体
    • 2.网页基础
      • 2.1.网页的组成
        • 2.2.网页的结构
        • 2.3.选择器
    • 3.爬虫基本原理
      • 3.1.爬虫概述
        • 3.1.1.获取网页
        • 3.1.2.提取信息
        • 3.1.3.保存数据
        • 3.1.4.自动化程序
      • 3.2. 能抓怎样的数据
      • 3.3.JavaScript渲染页面
    • 4.会话和Cookies
      • 4.1.静态网页和动态网页
      • 4.2.无状态HTTP
        • 4.2.1.会话
        • 4.2.2.Cookies
        • 4.2.3.会话维持
        • 4.2.4.属性结构
        • 4.2.5.会话Cookie和持久Cookie
        • 4.2.常见误区
    • 5.代理的基本原理
      • 5.1.基本原理
      • 5.2.代理的作用
      • 5.3.代理分类
      • 5.4.常见代理设置
  • 二、基本库的使用
    • 1.使用urlliib库
      • 1.1.发送请求
        • 1.1.1.request.urlopen()
        • 1.1.2.Request
        • 1.1.3.高级用法
      • 1.2.处理异常
        • 1.2.1.URLError
        • 1.2.2.HTTPError
      • 1.3.解析链接
        • 1.3.1.urlparse()
        • 1.3.2.urlunparse()
        • 1.3.3.urlsplit()
        • 1.3.4.urlunsplit()
        • 1.3.5.urljoin()
        • 1.3.6.urlencode()
        • 1.3.7.parse_qs()
        • 1.3.8.parse_qsl()
        • 1.3.9.quote()
        • 1.3.10.unquote()
    • 2.使用 requests
      • 2.1.基本使用
      • 2.2.高级用法
        • 2.2.1.文件上传
        • 2.2.2.设置Cookies
        • 2.2.3.会话维持
        • 2.2.4.SSL证书验证
        • 2.2.5.代理设置
        • 2.2.6.身份认证
        • 2.2.7.Prepared Request
    • 3.正则表达式使用
  • 三、解析库的使用
    • 1.使用XPath
    • 2.使用Beautiful Soup
  • 四、文件储存
    • 1.TXT文件存储
    • 2.JSON文件存储
    • 3.CSV文件存储
      • 3.1.写入
      • 3.2.读取
    • 4.关系型数据库存储
      • 4.1.存入SQL Sever
    • 5.存入非关系型数据库
      • 5.1.存入MongoDB
      • 5.2.存入 Redis
  • 五、Ajax 数据抓取
    • 1.什么是 Ajax
      • 1.1.发送请求
      • 1.2.解析内容
      • 1.3.渲染页面
    • 2.Ajax分析方法
      • 2.1.查看请求
      • 2.2.过滤请求
    • 3.Ajax结果提取
  • 六、动态渲染页面的抓取
    • 1.Seleium的使用
      • 1.1.基本使用
      • 1.2.声明浏览器对象
      • 1.3.访问页面
      • 1.4.查找节点
      • 1.5.节点交互
      • 1.6.动作链
      • 1.7.执行 JS
      • 1.8.获取节点信息
      • 1.9.切换Frame
      • 1.10.延时等待
      • 1.11.前进和后退
      • 1.12.Cookies
      • 1.13.选项卡管理
      • 1.14.异常处理
      • 1.15.实战
    • 2.Splash的使用
      • 2.1.Splash Lua 脚本
      • 2.2.Splash对象属性
      • 2.3.Splash对象的方法
        • **go()**
        • **wait()**
        • **jsfunc()**
        • evaljs()
        • runjs()
        • **autoload()**
        • **call_later()**
        • http_get()
        • **http_post()**
        • **set_content()**
        • html()
        • png()
        • jpeg()
        • har()
        • url()
        • get_cookies()
        • add_cookie()
        • clear_cookies()
        • get_viewport_size()
        • set_viewport_size()
        • set_viewport_full()
        • set_user_agent()
        • set_custom_headers()
        • select()
        • select_all()
        • mouse_click()
      • 2.3.Splash API 调用
        • render.html
        • render.png
        • render.jpeg
        • render.har
        • render.json
        • execute
    • 3.Splash 负载均衡配置
  • 七、验证码的识别
    • 1.图形验证码
    • 2.极验滑动验证码
    • 3.点触验证码
  • 八、代理的使用
    • 1.在在请求模块中使用
    • 2.代理池的维护
  • 九、Scrapy
    • 1.文件爬虫
      • **1.1.定义文件字段**
      • **1.2.修改管道文件**
        • **1.2.1.文件名在文件的url中**
        • **1.2.2.文件名自定义**
      • **1.3.启动配置,指定下载路径**
      • **1.4.实现爬虫**
        • 1.4.1.链接提取类
        • **1.4.2.链接构造类**
      • **1.5.统一缩进方式**
    • 2.模拟登录
      • 2.1.使用FormRequest(可能不可行)
      • 2.2.使用在Request添加cookies参数
    • 3.爬取动态网页
        • 3.1.使用render.html进行渲染(无触发事件型)
          • 3.1.1.开启Splash渲染服务
          • 3.1.2.发送SplashRequest请求
          • 3.1.3.修改配置文件
        • 3.2.使用execute.html渲染服务(触发JS代码)
          • 3.2.1.开启Splash渲染服务
          • 3.2.2.指定爬取范围,先触发JS代码返回HTML页面再进行爬取
          • 3.2.3.修改配置
        • 3.3.点击加载更多
          • 3.3.1.在抓取页面抓包Network,寻找 Type 为 xhr ,initiator 为 jquery.js:3 的 url
          • 3.3.2.定义字段
          • 3.3.3.启动配置
          • 3.3.4.模拟发送 jQuery 请求 (见 一.4.1 链接构造类)
    • 4.存入数据库
      • 4.1.存入MongoDB
        • 4.1.1.定义字段
        • 4.1.2.修改管道文件
        • 4.1.3.启动配置
        • 4.1.4.实现爬虫(见4.3.3)
      • 4.2.存入Redis
        • 4.2.1.启动Redis-sever服务
        • 4.2.2.定义字段
        • 4.2.3.启动配置
        • 4.2.4.实现爬虫(见4.3.3)
      • 4.3.存入自定义自定义导出(以实现Excel导出为例,Excel导出Scrapy未定义)
        • 4.3.1.在settings.py文件同级目录下实现my_exporters
        • 4.3.2.启动配置
        • 4.3.3.实现爬虫
        • 4.3.3.实现爬虫

一、爬虫基础

1.HTML基本原理

1.1.URI和URL

URI 统一资源标识符, URL 统一资源定位符

URL是URI的子集,每个URL都是URI,URI还包括一个子类叫URN,统一资源名称,URN只命名资源而不定位资源,如书的 ISBN。在目前的互联网中,URN非常少,几乎所有的URI都是URL

1.2.超文本

浏览器中看到的网页都是超文本解析而成的,源代码是一系列的HTML代码,HTML就可以称作超文本

1.3.HTTP和HTTPS

URL的开头有http或https,有时还有ftp,sftp,smb开头的URL,他们都是协议类型,通常抓取的页面就是http或https协议的

HTTP 超文本传输协议,从网络传输超文本到本地浏览器的传送协议,保证高效而准确地传送超文本文档,由万维网协会和internet工作小组IETF共同合作制定的规范。

HTTPS 是以安全为目标的HTTP通道,简单讲是HTTP的安全版,即HTTP下加入SSL层,简称HTTPS

HTTPS的安全基础是SSL,因此通过它传输的内容都是经过SSL加密的。

某些网站虽然使用了HTTPS协议,但还是会被浏览器提示不安全,如 https://www.1230cn ,因为12306的CA证书是由中国铁道部自行签发的,而这个证书是不被CA机构信任的,但它的数据实际上是经过SSL加密的。如果要爬取这样的站点,就要设置忽略证书的选项,否则会提示SSL链接错误

1.4.HTTP请求过程

我们在浏览器中输入一个URL,回车之后便会看到页面内容,实际上,这个过程是浏览器向网站所在的服务器发送了一个请求,网站服务器接收到这个请求后进行处理和解析,然后返回响应内容,接着传送会浏览器,响应里包含了页面的源代码等内容,浏览器再对其进行解析,使网页呈现了出来。在Network页面下方的一个个条目代表着一个个发送请求和接收响应的过程。

Name:请求名称,一般会把URL的最后一部分当作名称

Status:响应的状态码,显示为200代表响应是正常的

Type:请求的文档类型,document代表请求的是一个HTML文档,内容就是一些HTML代码

Initiator:请求源,用来标记请求是哪个对象或进程发起的

Size:从服务器下载的文件和请求的资源大小,如果是从缓存中取得的资源则会显示为 from catch

Time:发起请求到获取响应所用的总时间

Waterfall:网络请求的可视化流瀑布

点击条目可以看到更详细的信息

Python爬虫_第1张图片

首先是General部分,Request为URL请求的URL,Request Method为请求的方法,Status Code为响应的状态码,Remote Address为远程服务器的地址和端口,Referrer Policy 为 Referrer 判别策略。再往下是Response Headers 和Request Headers,代表响应头和请求头,服务器会根据请求头内的信息判断请求是否合法,进而作出对应的响应。

1.5.请求

1.5.1.请求方法

常见的请求方法由两种,GET和POST

在浏览器中输出URL并回车就发起了一个GET请求,请求的参数会直接包含到URL里,POST请求大多在表单提交时发起,比如对于一个登录表单,输入用户名和密码后点击登录,通常会发起一个POST请求,数据以表单的形式传输,而不会体现在URL中。

对比如下:

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

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

除GET和POST之外,还有一些请求方法如HEAD,PUT,DELETE,OPTIONS,CONNECT,TRACE等

1.5.2.请求的网址

请求的网址即统一资源定位符 URL,它可以唯一确定我们想请求的资源

1.5.3.请求头

Python爬虫_第2张图片

请求头用来说明服务器要使用的附加信息

Accept:请求报头域,指定客户端可接受哪些类型的信息

Accept-Encoding:指定客户端可接受的内容编码

Accept-Language:指定客户端可接收的语言类型

Host:用于指定请求资源的主机IP和端口号,其内容为请求URL的原始服务器或网关的位置

Cookie:也常用复数形式Cookies,这是网站为了辨别用户进行会话跟踪而存储在用户本地的数据

Reference:用来标识这个请求是从哪个页面发过来的,服务器可以拿到这一信息并做相应的处理,如做来源统计,防盗链处理

User-Agent:简称UA,它是一个特殊字符串头,可以使服务器识别客户使用的操作系统及版本,浏览器及版本信息

Content-Type:也叫互联网媒体类型或者MME类型,如text/html代表HTML格式,image/gif代表GIF图片,application/json代表JSON类型

1.5.4.请求体

请求体一般承载的内容是POST请求中的表单数据,而对于GET请求,请求体则为空

Content-Type和POST提交数据的方式有关,在爬虫中,如果要构造POST请求,需要使用正确的Content-Type类型,并了解各种请求库的各个参数设置时使用的时哪种Content-Type,不然可能会导致POST提交后无法正常响应

1.响应

1.1.响应状态码

响应状态码表示服务器的响应状态,在爬虫中可以根据状态码来判断服务器的响应状态

1.2.响应头

Date:标识响应产生的时间

Last-Modified:指定资源的最后修改时间

Content-Type:文档类型,指定返回的数据类型

Set-Cookie:设置Cookie。响应头中的Set-Cookie告诉浏览器需要将此内容放在Cookies中,下次请求携带Cookies请求

Expires:指定响应的过期时间,可以使代理服务器或浏览器将加载的内容更新到缓存中,如果再次访问时,就可以直接从缓存中加载,降低服务器负载,缩短加载时间。

1.3.响应体

在开发者工具中点击Preview,就可以看到网页源代码,也就是响应体的目标
Python爬虫_第3张图片

2.网页基础

2.1.网页的组成

网页可以分为三大部分:HTML,CSS 和 JavaScript 。如果网页是一个人的话,HTML 是骨架,CSS是皮肤,JS是肌肉。

HTML全称是超文本标记语言,它是描述网页的一种语言,各种复杂的元素用不同类型的标签来表示,各种标签通过不同的排列和嵌套形成了网页的框架

CSS叫层叠样式表通过选择器和样式规则定义每个标签的样式

JavaScript简称JS,是一种脚本语言,网页中的一些交互和动画效果,如下载进度条,提示框,轮播图等就是JS的功劳,它定义了网页的行为,实现了用户的信息直接的实时动态和交互

2.2.网页的结构

在HTML中,所有标签定义的内容都是节点,它们构成了一个HTML DOM树

DOM是W3C(万维网联盟)的标准,全称是文档对象模型,它定义了HTML和XML文档的标准,根据HTML DOM的规定,HTML文档中的所有内容都是节点,整个文档是一个文档节点,每个HTML元素是元素节点,HTML元素内的文本是文本节点,每个HTML属性是属性节点,注释是注释节点

2.3.选择器

选择器

3.爬虫基本原理

3.1.爬虫概述

3.1.1.获取网页

通过urllib、requests等可以帮助我们实现HTTP请求操作,请求和响应都可以用类库提供的数据结构来表示,得到响应部分后只需要解析数据结构中的Body部分即可,即得到网页的源代码

3.1.2.提取信息

最通用的方法是采用正则表达式提取,这是一个万能的方法,但是比较复杂且容易出错。另外由于网页的结构由一定的规则,所有可以根据网页节点属性,CSS选择器或Xpath来提取网页信息的库,如BeautifulSoup,pyquery,lxml等

3.1.3.保存数据

提取信息后,一般会将提取到的数据保存到某处以便后续使用。这里保存的形式由多种多样,如可以简单保存为TXT文本或JSON文本,也可以保存到数据库,如MySQL和MongoDB等,也可以保存至远程服务器,如借助SFTP进行操作等

3.1.4.自动化程序

爬虫就是代替我们快速获取大量数据的自动化程序,它可以在抓取过程中进行各种异常处理,错误重试等操作,确保爬取高效进行

3.2. 能抓怎样的数据

在网页中能看到各种各样的信息,最常见的便是常规网页,它们对应着HTML代码,而最常见的便是HTML源代码,另外还有些网页返回的不是HTML代码,而是一个JSON字符串,这种格式方便传输和解析,数据提取也更方便。此外,还有各种二进制数据,如图片,视频,音频,也可以利用爬虫爬取并保存,还有其他各种扩展名的文件,只要在浏览器里可以访问到,就可以将其抓取下来

3.3.JavaScript渲染页面

有时候,用urllib或requests抓取网页时,得到的源代码实际和浏览器中看到的不一样,这是一个非常常见的问题。现在网页越来越多地采用 Ajax,前端模块化工具来构建,整个网页可能都是JS渲染出来的,也就是说原始的HTML代码就是一个空壳。对于这样的情况,可以分析其后台的Ajax接口,也可以使用Selenium,Splash这样的库来实现 JS 渲染。

4.会话和Cookies

在浏览网站时,经常遇到需要登录的情况,有些页面只有登录才能访问,而且登录之后可以连续访问很多网站,但有时候过一段时间就需要重新登录,还有一些网站在打开浏览器时就自动登录了,而且很长时间都不会失效,这些就涉及了会话(Session)和Cookies的相关知识

4.1.静态网页和动态网页

一个基本的HTML代码将其保存为一个.html文件后把它放在某台具有固定公网IP的主机上,主机装有Apache或Nginx等服务器,这台主机就可以作为服务器了,其他人便可以通过访问服务器而看到这个页面,这就搭建了一个网站。这种网页是HTML代码编写的,文字,图片等内容均通过写好的HTML代码来指定,这种页面叫做静态页面,它加载速度快,编写简单,但存在很大的缺陷,如可维护性差,不能根据URL灵活多变地显示内容。因此动态网页应运而生,它可以动态解析URL中参数的变化,关联数据库并动态呈现不同的页面内容,它们不再是一个简单的HTML,而可能是由JSP,PHP,Python等语言编写的,其功能更丰富,另外动态网站还可以实现用户登录和注册的功能。

4.2.无状态HTTP

HTTP的无状态是指HTTP协议对事务处理是没有记忆能力的,也就是说服务器不知道客户端是什么状态,当我们向服务器发送请求后,服务器解析此请求,然后返回对应的响应,服务器负责完成整个过程,而且这个过程是完全独立的,服务器不会记录前后的状态变化,也就是缺少状态记录,这意味着如果后续要处理前面的信息,则必须重传,着导致需要额外传递前面的一些重复请求,才能获取后续响应,这太浪费资源,而且对于需要用户登录的页面更是棘手。

这时两个用于保持HTTP连接状态的技术就出现了,它们分别是会话和Cookies。会话在服务端,也就是网站的服务端,用来保存用户的会话信息;Cookies在客户端,也可以理解为浏览器端,有了Cookies,浏览器在下次访问网页时会自动附带上它发送给服务器,服务器通过识别Cookies并鉴定是哪个用户,然后判断用户是否是登录状态,然后返回对应的响应。

4.2.1.会话

会话,其本来的含义是指有始有终的一系列动作/消息。比如,打电话时,从拿起电话拨号到挂断电话这中间的一系列过程可以称为一个会话。而在Web中,会话对象用来存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序和Web页之间跳转时,存储在会话对象中的变量不会丢失,而是在整个用户会话中一直存在下去,当用户请求来自应用程序的Web页时,如果该用户还没有会话,则Web服务器将会自动创建一个会话对象。当会话过期或被放弃后,服务器将终止该会话。

4.2.2.Cookies

Cookies指某些网站为了识别用户身份,进行会话跟踪而存储在用户本地终端上的数据。

4.2.3.会话维持

当客户端第一次请求服务器端时,服务器会返回一个请求头中带有Set-Cookie字段的响应给客户端,用来标记是哪一个客户,客户端浏览器会把Cookies保存起来。当浏览器下一次再请求该网站时,浏览器会把此Cookies放到请求头一起提交给服务器,Cookies携带了会话的ID信息,服务器检查该Cookies即可找到对应的会话是什么,然后再判断会话来以此来判断用户状态。

成功登录某个网站时,服务器会告诉客户端要设置哪些Cookies信息,在后续访问页面客户端会把Cookies发送给服务器,服务器再找到对应的会话加以判断。如果会话中的某些设置登录状态的变量是有效的,那就证明用户处于登录状态,此时返回登录之后才能查看的网页内容,浏览器进行解析后即可看到网页的具体内容。

反之,如果传给服务器的Cookies是无效的,或者会话已经过期了,我们将不能继续访问页面,此时可能会收到错误的响应或者跳转到登录页面重新登录。

所以,Cookies和会话需要配合,一个处于客户端,一个处于服务端,二者共同协作,就实现了登录会话控制

4.2.4.属性结构

Python爬虫_第4张图片

在开发者工具中打开Application选项卡,然后在左侧会有一个Storage部分,最后一项即为Cookies,点开的每个条目都可以称为Cookie,它有如下几个属性

Name:该Cookie的名称,一旦创建,该名称便不可更改

Value:该Cookie的值,如果值为Unicode字符,需要为字符编码,如果值为二进制,则需要用BASE64编码

Domain:可以访问该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等,在网络上传输数据之前先将数据加密,默认为faslse

4.2.5.会话Cookie和持久Cookie

会话Cookie放在浏览器内存里,浏览器在关闭之后改Cookie即失效,持久Cookie则会保存到客户端的硬盘中,下次还可以继续使用,用于长久保存登录状态。其实严格来说没有会话Cookie和持久Cookie之说,至少Cookie的Max Age和 Expires字段决定了过期的时间

4.2.常见误区

除非程序通知服务器删除一个会话,否则服务器会一直保留,即使关闭浏览器,会话可能有也不会消失,之所以会产生 ’关闭浏览器,会话就会消失 ‘的错觉是因为大部分会话机制使用会话Cookie来保存会话ID信息,它在关闭浏览器后就消失了。由于关闭浏览器不会导致会话删除,这就需要服务器会话设置一个失效时间,当距离客户端上一次使用会话超过这个会话的四化建时,服务器就可以认为服务端已经停止了活动,才会把会话删除以节省存储空间

5.代理的基本原理

服务器可能会检测某个IP在单位时间内的请求次数,如果超过了阈值,就会直接拒绝服务,返回一些错误信息,这种情况可以称为封IP,可以通过伪装IP来防止

5.1.基本原理

代理实际上是指代理服务器,它的功能是代理网络用户去取得网络信息,形象地说,它是网络信息的中转站。当我们正常请求一个网站时,是仿宋给Web服务器,Web服务器把响应传回给我们。如果设置了代理服务器,此时本机不是直接向Web服务器发起请求,而是发给代理服务器,然后由代理服务器再发给Web服务器,接着代理服务器再把Web服务器返回的响应转发给我们的IP,这就是代理的原理

5.2.代理的作用

1.突破自身IP访问限制,访问一些平时不能访问的站点

2.访问一些单位或团体内部资源

3.提高访问速度

4.隐藏真实IP

5.3.代理分类

代理分类

5.4.常见代理设置

1.使用网上免费代理:现在基本没了

2.使用付费代理:网上存在许多代理商,可以付费使用

3.ADSL拨号:拨一次号换一次IP,稳定性高

二、基本库的使用

1.使用urlliib库

urllib库是Python内置的HTTP请求库,官方文档链接:https://docs.python.org/3/library/urllib.html

内置四个模块:

reuquest:最基本的HTTP请求模块,用来模拟发送请求

error:异常处理模块,捕获异常,然后进行重试或其他操作以保证程序不会意外终止

parse:工具模块,提供了许多 URL 处理办法,比如拆分,解析,合并

robotparser:主要用来识别网站的robots.txt文件,判断哪些网站可以爬,哪些网站不可以

1.1.发送请求

1.1.1.request.urlopen()

request.urlopen(url,data=None,[timeout,]*,cafile=None,capth=None,cadefault=False,context=None)

data参数

import urllib.parse
import urllib.request
data=bytes(urllib.parse.urlencode({'word':'hello'},encoding='utf-8'))
#urlencode()将参数字典转化为字符串,利用 bytes() 转化为字节流
response=urllib.request.urlopen('http://httpbin.org/post',data=data)
# httpbin.org 站点可以测试POST请求,参数出现在 form 字段中,表明模拟了表单提交,以 POST 方式传输数据 
print(response.read())
#b'{\n  "args": {}, \n  "data": "", \n  "files": {}, \n  "form": {\n    "word": "hello"\n  }, \n  "headers": {\n    "Accept-Encoding": "identity", \n    "Content-Length": "10", \n    "Content-Type": "application/x-www-form-urlencoded", \n    "Host": "httpbin.org", \n    "User-Agent": "Python-urllib/3.9", \n    "X-Amzn-Trace-Id": "Root=1-60248a78-54f8e3225c1776960285ea7d"\n  }, \n  "json": null, \n  "origin": "171.127.247.145", \n  "url": "http://httpbin.org/post"\n}\n'

指定 data 参数后,请求方式就由 GET 方式转化为 POST 方式 ,data 接收二进制数据

timeout参数指定超时时间,context 必须是 ssl.SSLContext 类型,指定 SSL 设置

cafile 和capath 指定CA证书和它的路径,请求HTTPS链接时会有用

cadefault 参数现在已经弃用,默认为 False

1.1.2.Request

urllib.request.urlopen()的url参数可以时一个 Request 类型的对象,可以通过 Request 来构造

class urllib.request.Request(url,data=None,headers={},origin_req_host=None,unverifiable=False,method=None)

data参数

见 二.1.1.1 data 参数

headers参数

headers是一个字典,表示请求头,可以在构造请求时通过 headers 参数直接构造,也可以通过请求实例的 add_header()方法添加

添加请求头最常用的方法是通过修改 User-Agent 来伪装浏览器,默认为 Python-urllib

origin_req_host参数

指定请求方的 host 名称 或者 IP 地址

unverifiable参数

此参数表示这个请求是否是无法验证的,默认是False,意思就是说用户没有足够权限来选择接收这个请求的结果

method参数

接收字符串,用来指定请求使用的方法,如 GET ,POST ,PUT 等

from urllib import request,parse
url='http://httpbin.org/post'
dic={
    'name':'YS'
}
data=bytes(parse.urlencode(dic),encoding='utf-8')
req=request.Request(url=url,data=data,method='POST')
req.add_header('User-Agent','ozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36')
response=request.urlopen(req)
print(response.read().decode('utf-8'))
'''{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {
    "name": "YS"
  }, 
  "headers": {
    "Accept-Encoding": "identity", 
    "Content-Length": "7", 
    "Content-Type": "application/x-www-form-urlencoded", 
    "Host": "httpbin.org", 
    "User-Agent": "ozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36", 
    "X-Amzn-Trace-Id": "Root=1-60249488-11afc9ff3518f4db3c71661d"
  }, 
  "json": null, 
  "origin": "171.127.247.145", 
  "url": "http://httpbin.org/post"
}'''
1.1.3.高级用法

对于一些高级操作,比如Cookies处理,代理设置等就需要用到 Handler,可以理解为各种处理器

urllib.request模块里的 BaseHandler 类是所有其他 Handler 的父类,提供了最基本的方法

有各种 Handler 子类继承这个 BaseHandler 类:

HTTPDefaultErrorHandler:用于处理 HTTP响应错误,错误都会抛出 HTTPError 类型的错误

HTTPRedirectHandler:用来处理重定向

HTTPCookieProcessor:处理Cookies

ProxyHandler:用来设置代理,默认为空

HTTPPasswordMgr:用来管理密码,它维护了用户名和密码的表

HTTPBasicAuthHandler:处理管理认证,如果打开一个链接时需要认证,可以用它来解决认证问题

更多代理

还有一个比较重要的类就是OpennerDirector,可以称为Openner,要实现更高级的功能,需要深入一层进行配置,要用更底层的实例来完成操作,因此必须引入 Openner。利用 Handler 来构建 Openner 再使用 open()方法

对于需要认证,登录的页面可以借助 HTTPBasicAuthHandler完成

from urllib.request import HTTPPasswordMgrWithDefaultRealm,HTTPBasicAuthHandler,build_openner
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)
#实例化HTTPBasicAuthHandler对象,参数是HTTPPasswordMgrWithDefaultRealm对象,这样建立了一个处理验证的 Handler
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)

在爬虫时添加代理

'''import urllib.request
def handler_openner():
	#系统的urlopen并没有添加IP代理的功能,需要自己定义
	#安全 套接层 ssl第三方的CA数字证书
	#http 80 端口和https的 443 端口
	#urlopen通过handler处理器(IP)和openner请求数据

	#创建自己的处理器
	handler=urllib.request.HTTPHandler()
	#创建自己的openner
	openner=urllib.request.build_opener(handler)
	#用自己创建的openner调用open方法
	respones=openner.open(url)
def creat_proxy_handler():
	#添加代理
	proxy={
	#免费的写法
	"http":"http://120.77.249.46:8080",

	}

	proxy_handler=urllib.request.ProxyHandler(proxy)
	openner=urllib.request.build_opener(proxy_handler)
	return data=openner.open(url).read()'''

#付费的代理发送

import urllib.request
#1.用户名和密码
#通过验证的处理器来发送
def money_proxy_use():
	#一.第一种方法
	#1.代理ip
	money_proxy={"http":"username:password@IP地址(带端口)"}
	#2.创建代理器
	proxy_handler=urllib.request.ProxyHandler(money_proxy)
	#3.通过处理器创建openner
	urllib.request.build_opener(proxy_handler)
	#4.open发送请求
	openner.open("url")
	#二.第二种方法
	user_name="adcname"
	pwd="123456"
	proxy_money="IP地址(带端口)"
	#创建密码管理器,添加用户名和密码
	password_manager=urllib.request.HTTPasswordMgrWitDefaultRealm()
	#url定位  uri>url
	#url 资源定位符
	password_manager.add_password(None,proxy_money,user_name,pwd)
	#创建可以验证代理ip的处理器
	handler_auth_proxy=urllib.request.ProxyBasicAuthHandler(password_manager)
	#根据处理器创建openner
	openner_auth=urllib.request.build_opener(handler_auth_proxy)
	#发送请求
	response=openner_auth.open("url")

#爬取自己公司的数据,做数据分析  #auth
def auth_nei_wang():
	#1.用户名密码
	user="admin"
	pwd="admin123"
	nei_url="http://192.6"
	#2.创建密码管理器
	pwd_manager=urllib.request.HTTPasswordMgrWitDefaultRealm()
	pwd_manager.add_password(None,nei_url,pwd)
	#创建认证处理器(用requests更多)
	urllib.request.HTTPBasicAuthHandler(pwd_manager)
	auth_handler=urllib.requestHTTPBasicAuthHandler(pwd_manager)
	openner=urllib.request.build_opener(auth_handler)
	response=openner.open(nei_url)

处理Cookies

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)
'''
BAIDUID=A03FA96987144827144869366D258EEC:FG=1
BIDUPSID=A03FA9698714482759ECC9C7AB0B34D3
H_PS_PSSID=33425_33403_33344_33461_33584_26350_33544
PSTM=1613012166
BDSVRTM=0
BD_HOME=1
'''
#输出Cookie文本格式
filename='cookie.txt'
cookie=http.cookiejar.MozillaCookieJar(filename)
#cookie=http.cookiejar.LWPCookieJar(filename)
#保存格式不同,保存成libwww-perl(LWP)格式的Cokies
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)
利用libwww-perl(LWP)格式的Cokies的Cookies
cookie=http.cookiejar.LWPCookieJar()
#读取本地的Cookies文件,获取Cookies的内容,
cookie.load('cookie.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')

1.2.处理异常

1.2.1.URLError

URLError类来自urllib库的error模块,继承自OSError类,是error异常模块的基类,由 request模块产生的异常都可以通过捕获这个类来处理,它由一个属性 reason 属性,即返回错误的原因

from urllib import request,error
try:
    response=request.urlopen('https:jiade.com')
except error.URLError as e:
    print(e.reason)
# no host given
1.2.2.HTTPError

它是URLError的子类,专门用来处理HTTP请求错误,比如认证失败,请求失败等,它由三个属性:

code:返回HTTP状态码

reason:同父类一样,返回错误原因

headers:返回请求头

1.3.解析链接

1.3.1.urlparse()

改方法可以实现 URL 的识别和分段

from urllib.parse import urlparse
result=urlparse('http:baidu.com/index.html;user?id=#comment') 
print(type(result),result)
# ParseResult(scheme='http', netloc='', path='baidu.com/index.html', params='user', query='id=', fragment='comment')
#scheme代表协议,netloc代表域名,path代表路径,params代表参数,query代表查询条件,#为锚点,直接定位页面的下拉部分

url.parse.urlparse(urlstring,scheme=‘’,allow_fragments=True)

urlstring:必填项,待解析的URL

scheme:默认的协议,假如URL链接没有带协议信息,会将这个作为默认的协议

allow_fragments:是否忽略 fragment 如果设置为 False,fragment部分会被忽略,它会被解析为path,parameters或query的一部分

1.3.2.urlunparse()

它接收一个可迭代对象,长度必须是 6 ,用于构造 URL

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
1.3.3.urlsplit()

此方法和urlparse()方法非常类似,只是它不再单独解析params这一部分,只返回 5 个结果,params会合并到path中

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

传入长度为5的可迭代对象,组成完整url

1.3.5.urljoin()

有了 urlunparse()和 urlsplit()方法可以完成链接的合并,但必须有固定的长度

urljoin()是生成链接的另一个方法,该方法需要base_url(基础链接)和新链接,分析base_url的scheme,netloc和path三个内容,并对新链接缺少的部分进行补充。如果新的连接里这三项不存在,就予以补充,如果存在就使用新连接的部分,而base_url终的params,query和 fragment是不起作用的

1.3.6.urlencode()

urlencode()可以将字典转化为 GET 请求的参数

from urllib.parse import urlencode
params={
	'name':'YS',
	'age':'20'
}
base_url='http://www.baidu.com?'
url=base_url+urlencode(params)
print(url)
#http://www.baidu.com?name=YS&age=20
1.3.7.parse_qs()

和urlencode()相反,将参数内容转化为字典

from urllib.parse import parse_qs
query='name=YS&age=20'
print(parse_qs(query))
#{'name': ['YS'], 'age': ['20']}
1.3.8.parse_qsl()

与parse_qs()不同的是它将参数转化为元组组成的列表

from urllib.parse import parse_qsl
query='name=YS&age=20'
print(parse_qsl(query))
#[('name', 'YS'), ('age', '20')]
1.3.9.quote()

该方法将内容转化为 URL 编码的格式,当 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
1.3.10.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=壁纸

2.使用 requests

2.1.基本使用

Python爬虫_第5张图片

2.2.高级用法

2.2.1.文件上传
import requests
files={'file':open('favicon.ico','rb')}
r=requests.post('http://httpbin.org/post',files=files)

文件上传部分会有一个单独的files字段来标识内容,而不是在form字段中

2.2.2.设置Cookies
import requests
import requests
headers={
	'Cookie':'_zap=33c39d33-374f-48fd-a2de-6358a98d1e07; d_c0="AABckRdTjhKPTj7J97KhNpb9anaLYv7UOxw=|1611568886"; _xsrf=Jha8143Tiu2AZ8QgEOj2dBW6UEYaPYhA; tst=r; q_c1=0fc228405a8340768a9e6d99c171eb89|1612782635000|1612782635000; l_cap_id="M2YzNTBmNzk1ZGY0NDhjMTljMTdmMGRjMTdiYmQ3YjY=|1612782771|f97884e2c6576a0d5947c823a9c57bc37b2149b0"; r_cap_id="OTk5Y2RmOThhYmM4NDc4OWE3OGJiZjdiMTVhODBhOTQ=|1612782771|1a1a13ff5701d07d06794c025acc921e2f846ddb"; cap_id="YTU2ODE5MzgzY2IzNGExN2IyMWQ2ZTU0MWUwMDc2ZjQ=|1612782771|a9f09dbccd13f358479bf7cfdea68150a44ee1db"; captcha_session_v2="2|1:0|10:1612838700|18:captcha_session_v2|28:YzBndmViNnYzanZqa3VycGZqa2c=|6628743e8ee12336328d103475a52b969e71672b64236d9ec8cae6e74e6099ad"; captcha_ticket_v2="2|1:0|10:1612838707|17:captcha_ticket_v2|244:eyJhcHBpZCI6IjIwMTIwMzEzMTQiLCJyZXQiOjAsInRpY2tldCI6InQwM1A0S2tmZ2tPb1NwWi1DWF8yUExVQjBjd3phUElTRnM5bkNReDNiZU9FV0NfdXV5UE1VbEp3NDN2UWJ2VlZZSGtMeG80enlfc0R3ZTdpNVhEazZ6YVVhSTNoWXpDNjZDSU5pM1EwTXdpRzJ2cThzenU4bWtUUXcqKiIsInJhbmRzdHIiOiJAWE1zIn0=|c3e2e53e522c1abbbb247985cf25508f535bd3bee5e4fbc6c8b591826eace6cb"; z_c0="2|1:0|10:1612838708|4:z_c0|92:Mi4xc1Zsb0lBQUFBQUFBQUZ5UkYxT09FaVlBQUFCZ0FsVk5ORVVQWVFDbEFhLVdzT0pPcXlDcmNfODJBTlMwRENFMGhn|f3c39bbc0459475fbeb0d7587b2a8fc4fc17c1907412450dfe26ece37f1b0f81"; Hm_lvt_98beee57fd2ef70ccdd5ca52b9740c49=1612868386,1612929750,1612959789,1613030732; Hm_lpvt_98beee57fd2ef70ccdd5ca52b9740c49=1613030739; KLBRSID=fe78dd346df712f9c4f126150949b853|1613030739|1613030730',
	'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36'
}
r=requests.get('https://www.zhihu.com/people/sen-luo-mo-xiang-90-46',headers=headers)
print(r.text)
2.2.3.会话维持
import requests
login_url="https://www.yaozh.com/login"
member_url="https://www.yaozh.com/member/"
headers={
	"User-Agent":"Mozilla/5.0 (Linux; Android 0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Mobile Safari/537.36"
}
login_form_data={
	"username":"YS12345",
	"pwd":"ys12345",
	"formhash":"B2A8BFFA91",
	"backurl":"https%3A%2F%2Fwww.yaozh.com%2F"
}
session=requests.session()#类似于cookiejar功能自动保存cookie
#2.登录成功后带着有效的cookies访问请求目标
login_response=session.post(login_url,data=login_form_data)
data=session.get(member_url,headers=headers).content.decode()
with open("C:/Users/PC/Desktop/try.html","w",encoding="utf-8") as f:
	f.write(data)
2.2.4.SSL证书验证
import requests
#忽略证书认证
response=requests.get('https://www.1230cn',verify=False)
print(response.status_code)
#InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised
#200
2.2.5.代理设置
import requests
proxies={
    'http':'http://10.10.1.10:3128'
    'https':'https://10.10.1.10:1080'
}
response=requests.get('https://www.taobao.com',proxies=proxies)

如果代理需要使用 HTTP Basic Auth,可以用类似 http://user:password@host:port

import requests
proxies={
    'http':'http://user:[email protected]:3128/'
}
response=requests.get('https://www.taobao.com',proxies=proxies)

除了基本的HTTP代理外,requests还支持SOCKS协议的代理

import requests
proxies={
    'http':'socks5://user:password@host:port'
    'https':'socks5://user:password@host:port'
}
response=requests.get('https://www.taobao.com',proxies=proxies)
2.2.6.身份认证
import requests
from requests.auth import HTTPBasicAuth
auth=HTTPBasicAuth('username','password')
r=get('http://localhost:5000',auth=auth)
#或者直接使用
#r=get('http://localhost:5000',auth=('username','password'))

此外requests还提供了其他认证方式,如 OAuth 认证

2.2.7.Prepared Request

在urllib库中,我们可以将请求表达为一个数据结构,其中各个参数都可以通过一个Request对象来表示,在requests中这个数据结构就是Prepared Request

from requests import Request,Session
url='http://httpbin.org/post'
data={
    'name':'YS'
}
headers={
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36'
}
s=Session()
#构造 Request对象
req=Request('POST',url=url,data=data,headers=headers)
#转化为Prepared Request对象
prepped=s.prepare_request(req)
r=s.send(prepped)
print(r.text)
'''
{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {
    "name": "YS"
  }, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Content-Length": "7", 
    "Content-Type": "application/x-www-form-urlencoded", 
    "Host": "httpbin.org", 
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36", 
    "X-Amzn-Trace-Id": "Root=1-6024f412-161d7a69506d9cdf6ab02532"
  }, 
  "json": null, 
  "origin": "171.127.247.145", 
  "url": "http://httpbin.org/post"
}
'''

3.正则表达式使用

Python爬虫_第6张图片

三、解析库的使用

1.使用XPath

补充节点轴选择

from lxml import etree
import requests
r=requests.get('https://www.baidu.com')
text=r.content.decode('utf-8')
html=etree.HTML(text)
#选中第一个a节点的所有祖先节点
reslt=html.xpath('//a[1]/ancestor::*')
print(reslt)
#[, , , , , , , , , , ]
#选中第一个a节点的所有div祖先节点
result=html.xpath('//a[1]/ancestor::div')
print(result)
#[, , , , , ]
#选中第一个a节点的所有属性节点
result=html.xpath('//a[1]/attribute::*')
#选中第一个a节点的所有href属性为"link1.html"的子节点
result=html.xpath('//a[1]/child::a[@href="link1.html"]')
#获取第一个a节点的所有span子孙节点
result=html.xpath('//a[1]/descendant::span')
#获取第一个a节点后续所有节点中的第二个后续节点
result=html.xpath('//a[1]/following::*[2]')
#获取第一个a节点之后的所有后续同级节点
result=html.xpath('//a[1]/following-sibling::*')

2.使用Beautiful Soup

Python爬虫_第7张图片
Python爬虫_第8张图片

四、文件储存

1.TXT文件存储

Python爬虫_第9张图片

2.JSON文件存储

#1.轻量级的数据交互格式   2.对象用花括号表示 数组用方括号表示  3.必须用双引号  4.结尾不能有逗号   5.不能写注释   6.整个文件有且只有一个花括号或者方括号
import json
#1.字符串和字典,列表转换(+S)
#字符串(json)-->dict list
data='[{"name":"YS","age":"20"},{"name":"LS","age":"18"}]'
list_data=json.loads(data)
print(type(data))  #str
print(type(list_data))   #list 去掉外侧单引号且所有双引号转换为单引号
#dict list-->字符串
list2=[{"name":"YS","age":"20"},{"name":"LS","age":"18"}]
data_json=json.dumps(list2)
#.文件和字符串,列表转换
#写入json文件
json.dump(list2,open(path,"w"))
#读取json-->list dict
result=json.load(open(path))

import csv
#需求  json-->csv
#1.读,创建文件
json_fp=open(path,"r",encoding="utf-8")
csv_fp=open(path,"w",)

#2.提出表头和表的内容
data_list=json.load(json_fp)
sheet_title=data_list[0].keys()
sheet_data=[data.values() for data in data_list]

#3.csv写入器
writer=csv.writer(csv_fp)

#4.分别写入表头和内容
writer.writerow(sheet_title)
writer.writerows(sheet_data)

#5.关闭文件
json_fp.close()
csv_fp.close()

3.CSV文件存储

3.1.写入

import csv
#newline=''可以消除空行
with open('C:/Users/PC/Desktop/test.csv','w',newline='') as c:
    #初始化写入对象
	writer=csv.writer(c)
    #delimiter参数指定列与列间的分隔符,delimiter=' '使数据在一列内显示没个数据直接是空格
    #writer=csv.writer(c,delimiter=' ')
	writer.writerow(['id','name','age'])
	writer.writerow(['1','Mike','20'])
	writer.writerow(['2','Bob','35'])
    #writerows支持多行写入,参数是二维列表
	#writer.writerows([['1','Mike','20'],['2','Bob','35']])
	'''
	支持字典类型写入
	fieldnames=['id','name','age']
	writer=csv.DictWriter(c,fieldnames=fieldnames)
	writer.writeheader()
	writer.writerow({'id':'1','name':'Mike','age':'20'})
	writer.writerow({'id':'2','name':'Bob','age':'35'})

	'''

3.2.读取

import csv
with open('C:/Users/PC/Desktop/test.csv','r',newline='') as c:
	reader=csv.reader(c)
	for row in reader:
		print(row)
 '''
['id', 'name', 'age']
['1', 'Mike', '20']
['2', 'Bob', '35']

'''

4.关系型数据库存储

4.1.存入SQL Sever

5.存入非关系型数据库

5.1.存入MongoDB

import pymongo
mongo_py=pymongo.MongoClient()  	#连接数据库
db=mongo_py["six"]    
collection=db["stu"]     #或collection=mongo_py["six"][stu]
#插入
collection.insert_one({"name":"张三"})
collection.insert_many({"name":"李四"},{"age":14})
#删除
collection.delete_one({"name":"张三"})
collection.delete_many({"name":"李四"})
#改
collection.update({"name":"李四"},{"$set":{{"age":15}}})
collection.update_many()
#查
collection.find_one({"age":15})
match_list=collection.find({"age":15})
for match in match_list:
	print(match)
#关闭
mongo_py.close()

5.2.存入 Redis

Python爬虫_第10张图片

import redis
#1.链接数据库
client=redis.StrictRedis(host="127.0.0.1",port="6379",db=0)

#2.设置key

key="python"
#result 返回True或False
#3.string 增加
result=client.set(key,"1")  

#4.删除
#result=client.delete(key)

#5.改
result=client.set(key,"舒服")

#6.查

result=client.get(key).decode()  #不加decode返回二进制
print(result)

#7.查看所有键

result=client.keys() #可以加正则匹配

五、Ajax 数据抓取

有时在用 requests 抓取页面的时候,得到的结果可能和浏览器中看到的不一样,这是因为requests获取到的都是原始的 HTML 文档,而浏览器中的页面则是 JS 处理数据后生成的结果,这些数据的来源有多种,可能是通过 Ajax 加载的,也可能是包含在 HTML 文档中,也可能是经过 JS 和特定算法计算后生成的

对于第一种情况,数据加载是一种异步加载方式,原始的页面最初不会包含某些数据,原始页面加载完后,会再向某个服务器请求某个接口获取数据,然后数据才被处理而呈现到网页上,这其实就是发送了一个 Ajax 请求,折中形式的页面在越来越多,如果可以用 requests来模拟 Ajax 请求,就可以成功抓取了

1.什么是 Ajax

Ajax,异步的JS和XML,它不是编程语言,而是利用 JS在保证页面不被刷新,页面链接不改变的情况下与服务器交换数据并更新网页的技术。在浏览网页时有很多下滑查看更多的选项,这就是 Ajax 获取解析数据并呈现的过程。发送 Ajax 请求到网页更新的过程可以简单分为3步:发送请求,解析内容,渲染页面

1.1.发送请求

JS 对 Ajax 最底层的实现,实际上就是新建了 XMLHttpRequest对象,然后调用 onreadystatechange 属性设置了监听,然后调用 open()和send()方法向某个链接(也就是服务器)发送了请求,前面由于用 Python 实现请求发送后,可以得到响应结果,但这里请求的发送变成 JS来完成。由于设置了监听,当服务器返回响应时,onreadystatechange对应的方法便会被触发,然后在这个方法里解析响应内容即可

1.2.解析内容

得到响应之后,onreadystatechange属性对应的方法便会被触发,此时利用xmlhttp的responseText属性便可获取到响应内容,类似于Python中利用requests向服务器发起请求,然后得到响应的过程,返回内容可能是 HTML,也可能是 JSON,只需要在方法中用 JS 进一步处理即可,比如,如果是JSON的话,可以进行解析和转化

1.3.渲染页面

JS 有改变网页内容的能力,解析完响应内容后,可以调用 JS 对解析完的内容进行下一步处理了,通过 DOM 操作 进行增删改查即可。

2.Ajax分析方法

2.1.查看请求

在检查中的Network中找到Type为xhr,以getindex开头的选项,单击如下
Python爬虫_第11张图片

Python爬虫_第12张图片

在 Request Headers 中有一个信息名为X-Requested-With:XMLHttpRequest,这就标记了此请求是 Ajax请求,点击Preview即可看到响应内容,JS拿到这些数据后再执行相应的渲染方法,整个页面就渲染出来了
Python爬虫_第13张图片

也可以切换到Response选项卡观察到真实的返回数据(Preview中的数据经过了Chrome的自动解析,如转码等)
Python爬虫_第14张图片

在最开始的网页源代码中可以看到它非常简单,只是执行了一些 JS 代码,所以看到的真实数据其实都是Ajax发送请求后浏览器进一步渲染得到的。

2.2.过滤请求

在Network中点击XHR过滤,即可
Python爬虫_第15张图片

3.Ajax结果提取

观察一次次加载的请求链接

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

除第一个链接的containerid是100505+value(刚打开页面还未加载就出现)其他都是107603+value,继续加载能发现每一个新链接除since_id不一样外,其余都一样,因此可以构造since_id发送请求来爬取页面

字面意思可以理解到since_id标志着上一次加载到哪里,这一次从哪里开始加载,打开每一个链接的响应内容,可以看到返回内容为JSON
在这里插入图片描述

其中的内容便包含下一个链接的since_id,类似递推的关系,只要给出第一个链接,提取其相应内容,便可以构造出下一页的链接,从而爬取指定页面的内容

六、动态渲染页面的抓取

Ajax的分析和抓取其实也是 JS 动态渲染页面的一种情形,通过直接分析 Ajax ,我们仍然可以借助requests或urllib来实现数据抓取。不过 JS 动态渲染的页面不止 Ajax 这一种,有的网站分页部分是由 JS 生成的,并非原始的HTML代码,这其中不包含Ajax请求,还有淘宝页面,即使是用 Ajax 获取的数据,但是其 Ajax 接口有很多的加密参数,很难找出其规律,也很难直接分析 Ajax 来抓取

为了解决这些问题,我们可以直接使用模拟浏览器运行的方式来实现,这样就可以做到在浏览器中看到的是什么样,抓取的源码就是什么样,也就是可见即可爬。这样我们就不用再去管网页内部的 JS 代码用了什么算法渲染页面,不用管网页后台的 Ajax 接口到底有哪些参数

Python 提供了许多模拟浏览器运行的库,如Selenium,Splash,PyV8,Ghost等

1.Seleium的使用

Selenium是一个自动化测试工具,利用它可以驱动浏览器执行特定的动作,如点击,下拉等操作,同时还可以获取浏览器当前呈现的网页源代码,做到可见即可爬。对于一些 JS 动态渲染的页面来说,此种抓取方式非常有效

1.1.基本使用

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait

browser=webdriver.Chrome()

try:
	browser.get('https://www.baidu.com')
	inpu=browser.find_element_by_id('kw')
	inpu.send_keys('Python')
	inpu.send_keys(Keys.ENTER)
	wait=WebDriverWait(browser,10)
	wait.until(EC.presence_of_element_located((By.ID,'content_left')))
	print(browser.current_url)
	print(browser.get_cookies)
	print(browser.page_source)

finally:
	browser.close()

1.2.声明浏览器对象

Selenium支持非常多浏览器,如Chrome,Firefox,Edge,还有 Android,BlackBerry等手机端的浏览器,另外也支持无界面浏览器PhantomJS

from selenium import webdriver
browser=webdriver.Chrome()
browser=webdriver.Firefox()
browser=webdriver.Edge()
browser=webdriver.PhantomJS()
browser=webdriver.Safari()

这样就完成了浏览器对象的初始化,并将其赋值为browser对象,接下来调用browser对象,让其执行各个动作以模拟浏览器操作

1.3.访问页面

可以使用get()方法来请求网页,参数传入链接 URL 即可

from selenium import webdriver
browser=webdriver.Chrome()
browser.get('https://www.taobao.com')
print(browser.page_source)
browser.close()

1.4.查找节点

Selenium 可以驱动浏览器完成各种操作,比如填充表单,模拟点击等。如果想要完成向某个输入框输入文字的操作,总要指定输入框在哪里,而Selenium提供了一系列查找节点的方法,可以根据这些方法获取想要的节点

观察网页源代码,淘宝网首页收缩框的id是q,name也是q。此外还有其他许多属性,此时就可以用多种方式获取它了

from selenium import webdriver
browser=webdriver.Chrome()
browser.get('https://www.taobao.com')
input_first=browser.find_element_by_id('q')
input_second=browser.find_element_by_css_selector('#q')
input_third=browser.find_element_by_xpath('//*[@id="q"]')
print(input_first,input_second,input_third)
#完全一样的 
browser.close()

下面是所有获取单个节点的方法:

find_element_by_id
find_element_by_name
find_element_by_link_text
find_element_by_partial_link_text
find_element_by_tag_name
find_element_by_class_name
find_element_by_css_selector

另外还有通用方法 find_element(By,value) 比如 find_element_by_id(‘q’)等价于find_element(By.ID,‘q’)

要查找满足条件的所有结点要用find_elements()

1.5.节点交互

Selenium可以驱动浏览器来执行一些操作,比较常见的用法有输入文字:send_keys(),清空文字 clear(),点击按钮click(),执行时先选中输入框节点,再输入,或者选中点击按钮,执行click()

更多操作

1.6.动作链

还有一些操作没有特定的执行对象,比如鼠标拖拽,键盘按键等,这些动作用另一种方式来执行,那就是动作链

比如实现一个节点的拖拽操作,将某个节点从一处拖拽到另一处,可以这样实现

from selenium import webdriver
from selenium.webdriver import ActionChains
#初始化浏览器对象
browser=webdriver.Chrome()
#打开网页
browser.get('http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable')
#进入iframe
browser.switch_to_frame('iframeResult')
#选中被拖拽节点
source=browser.find_element_by_css_selector('#draggable')
#选中拖拽到的节点
target=browser.find_element_by_css_selector('#droppable')
#声明ActionChains对象并赋值为actions变量
actions=ActionChains(browser)
#调用方法
actions.drag_and_drop(source,target)
#执行
actions.perform()

更多操作

1.7.执行 JS

对于某些操作,Selenium API 并没有提供,比如下拉进度条,它可以直接模拟运行 JS,此时可以使用 execute_script()

from selenium import webdriver
from selenium.webdriver import ActionChains
browser=webdriver.Chrome()
browser.get('http://www.zhihu.com/explore')
browser.execute_script('window.scrollTo(0,document.body.scrollHeight)')
browser.execute_script('alert("TO Bottom")')

1.8.获取节点信息

通过page_source属性可以获取网页源代码,解咒就可以使用解析库解析,不过 Selenium 提供了选择节点的方法,返回的是 WebElement 类型,那么它也有相关的方法和属性来直接获取节点信息,如属性,文本等。

选中节点后可以使用get_attribute(属性)来获取属性,获取文本可以选用节点的.text属性

1.9.切换Frame

网页中有一种节点叫 iframe,也就是子 Frame,相当于页面的子页面,它的结构和外部网页的结构完全一致,Selenium 打开网页后,它默认是在父级的 Frame 里操作,此时如果还有子 Frame,它是不能获取到子 Frame 里面的节点的,这就需要 switch_to.frame()方法来切换

1.10.延时等待

在Selenium中,get()方法会在网页框架加载结束后结束执行,此时如果获取page_source,可能并不是浏览器完全加载完成的页面,如果某些网页有额外的 Ajax 请求,在网页源代码中也不一定能获取到,所有需要等待一定时间,确保节点已经加载完毕

隐式等待

隐式等待执行操作时,如果Selenium没有在DOM中找到节点,将继续等待,超出设定时间后,抛出找不到节点异常,默认事件是0

from selenium import webdriver
browser=webdriver.Chrome()
browser.implicitly_wait(10)
browser.get('https://www.zhihu.com/explore')

显示等待

隐式等待只是规定了一个固定时间,而页面的加载时间会受到网络条件的影响,还有一种更合适的显示等待方法,它指定要查找的节点,然后指定一个最长等待时间,如果在规定时间内加载了这个节点,就返回查找的节点,到规定时间未加载出节点,抛出超时异常

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
browser.get('https://www.baidu.com')
inpu=browser.find_element_by_id('kw')
inpu.send_keys('Python')
inpu.send_keys(Keys.ENTER)
#引入WebDriverWait对象,指定最长等待时间
wait=WebDriverWait(browser,10)
#调用方法指定加载确定节点
wait.until(EC.presence_of_element_located((By.ID,'content_left')))
#对于按钮可以更改等待条件EC.element_to_dlidkable(是否可点击)

其他等待条件

1.11.前进和后退

使用browser.back()和browser.forward()可以返回和前进页面

1.12.Cookies

使用Selenium,还可以方便地对 Cookies 进行操作,如获取,添加,删除 Cookies 等

from selenium import webdriver
browser=webdriver.Chrome()
browser.implicitly_wait(10)
browser.get('https://www.zhihu.com/explore')
print(browser.get_cookies())
browser.add_cookie({'name':'name','domain':'www.zhihu.com','value':'germey'})
print(browser.get_cookies())
browser.delete_all_cookies()
print(browser.get_cookies())

1.13.选项卡管理

在访问网页时,有时会开启一个选项卡,在Selenium中,我们也可以对选项卡进行操作

import time
from selenium import webdriver
browser=webdriver.Chrome()
browser.get('https://www.baidu.com')
#新开启一个选项卡
browser.execute_script('window.open()')
#获取当前开启的所有选项卡
print(browser.window_handles)
#跳转选项卡
browser.switch_to_window(browser.window_handles[1])
browser.get('https://www.taobao.com')
time.sleep(1)
browser.switch_to_window(browser.window_handles[0])
browser.get('https://python.org')

1.14.异常处理

在使用 Selenium 的过程中,难免遇到一些异常,例如超时,节点未找到等错误,一旦出现此类错误,程序便不会自动运行,可以使用try except 语句来捕获异常

1.15.实战

from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
import os
import time
import json

class initial():
	def browser_initial(self):
		#浏览器初始化
		browser=webdriver.Chrome()
		log_url='https://login.taobao.com/member/login.jhtml?redirectURL=http%3A%2F%2Fs.taobao.com%2Fsearch%3Fq%3Dipad%26imgfile%3D%26commend%3Dall%26ssid%3Ds5-e%26search_type%3Ditem%26sourceId%3Dtb.index%26spm%3Da21bo.2017.201856-taobao-item.1%26ie%3Dutf8%26initiative_id%3Dtbindexz_20170306&uuid=9ebb031412159b09770b9acf3cdbde3a'
		return log_url,browser
	def get_cookies(self,log_url,browser):
		#截获cookies并保存本地
		browser.get(log_url)
		time.sleep(15) #进行扫码
		dictCookies=browser.get_cookies()
		jsonCookies=json.dumps(dictCookies)
		with open('Cookies.txt','w',encoding='utf-8') as f:
			f.write(jsonCookies)
		print('cookies保存成功')
	def start(self):
		tur=self.browser_initial()
		self.get_cookies(tur[0],tur[1])
class Spider():
	def browser_initial(self):
		browser=webdriver.Chrome()
		goal_url='https://s.taobao.com/search?q=ipad&bcoffset=7&p4ppushleft=2%2C48&ntoffset=7&s=0'
		browser.get('https://login.taobao.com/member/login.jhtml?redirectURL=http%3A%2F%2Fs.taobao.com%2Fsearch%3Fq%3Dipad%26imgfile%3D%26commend%3Dall%26ssid%3Ds5-e%26search_type%3Ditem%26sourceId%3Dtb.index%26spm%3Da21bo.2017.201856-taobao-item.1%26ie%3Dutf8%26initiative_id%3Dtbindexz_20170306&uuid=9ebb031412159b09770b9acf3cdbde3a')
		return goal_url,browser
	def log_taobao(self,goal_url,browser):
		#读取本地cookies并登录目标网页
		with open('Cookies.txt','r',encoding='utf-8') as f:
			listCookies=json.loads(f.read())
		print('已经获取cookie')
		for cookie in listCookies:
			cookie_dict={
			'domain':'.taobao.com',
			'name':cookie.get('name'),
			'value':cookie.get('value'),
			'path':'/',
			'expire':cookie.get('expire'),
			"httpOnly":cookie.get("httpOnly"),
			'secure':cookie.get('secure'),
			}
			browser.add_cookie(cookie_dict)
		browser.get(goal_url)
		return browser
	def get_data(self,browser,page):
		print('get_data这里',browser)
		wait=WebDriverWait(browser,20)
		if page>1:
			inp=wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,'#mainsrp-pager > div > div > div > div.form > input')))
			submit=wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,'#mainsrp-pager > div > div > div > div.form > span.btn.J_Submit')))
			inp.clear()
			inp.send_keys(page)
			submit.click()
			#判断当前高亮页数是否为需求页数
			wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR,'#mainsrp-pager > div > div > div > ul > li.item.active > span'),str(page)))
			#判断商品是否加载完毕
			wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,'body')))
			html=browser.page_source
			print(html.decode('utf-8'))

	def start(self,page):
		tur=self.browser_initial()
		browser=self.log_taobao(tur[0],tur[1])
		print('start这里',browser)
		self.get_data(browser,page)

initial().start()
Spider().start(6)

2.Splash的使用

Splash 是一个 JS 渲染服务,一个带有HTTP API 的轻量级浏览器,同时对接了Python 中的 Twisited 和 QT库,利用它可以实现动态渲染页面的抓取

2.1.Splash Lua 脚本

Splash可以通过 Lua 脚本执行一系列渲染操作,这样就可以用Splash来模拟 Chrome的操作

入口及返回值

#方法名必须是main(),Splash会默认调用这个方法
function main(splash,args)
	splash:go('http://www.baidu.com')
    splash:wait(0.5)
    lacal title=splash:evaljs('document.title')
    return {title=title}
end
#返回网页标题

异步处理

Splash支持异步处理,但是并没有显示指明回调方法,其回调的跳转是在Splash内部完成的

function main(splash,args)
	local example_urls={'www.baidu.com','www.taobao.com','www.zhihu.com'}
    local urls= args.urls or example_urls
    local result={}
    for index,url in ipairs(urls) do
    	#字符串拼接是用 .. 而不是 +
    	local ok,reason=splash:go('http://'..url)
        if ok then
        	#当Splash执行到wait时会去处理其他任务,指定时间过后再回来继续处理
        	splash:wait(2)
            results[url]=splash:png()
        end
    end
    return results
end

2.2.Splash对象属性

main()方法的第一个参数是splash,这个对象相当于Selenium中的WebDriver对象,可以调用它的一些属性和方法来控制加载过程

下面是Splash对象的一些属性

args

该属性可以获取加载时配置的属性,比如URL,如果为GET请求,可以获得GET请求的参数;如果为POST请求,可以获取表单提交的数据

Splash也支持似乎用第二个参数直接作为args

function main(splash,args)
	local url=args.url
end

第二个参数args就相当于splash.args属性

js_enabled

这个属性是Splash的 JS 执行开关,默认为false

resource_timeout

设置加载的超时时间,设置为 0 或 nil (类似于Python中的None),代表不检测超时

images_enabled

设置图片是否加载,默认情况是加载的,禁用后可以节省网络流量并提高网页加载速度,但可能会影响 JS 渲染,因为禁用图片后,它外层的DOM节点高度会受影响,进而影响DOM节点的位置,如果 JS 对图片节点有操作的话,执行就会收到影响。

另外,Splash 使用了缓存,如果一开始加载了网页图片,之后禁用,再加载网页,之前加载好的网页可能还会显示,这时直接重启Splash即可

plugins_enabled

此属性可以控制浏览器插件是否开启,默认为false

scroll_position

设置此属性可以控制页面上下或左右滚动

function main(splash,args)
	assert(splash:go('https://www.taobao.com'))
    splash.scroll_position={y=400}
    return {png=splash:png()}
end

splash.scroll_position={x=100,y=200}

2.3.Splash对象的方法

go()

请求某个链接,可以模拟GET和POST请求,同时支持传入请求头,表单等数据

ok,reason = splash:go {url,baseurl=nil,headers=nil,http_method=‘GET’,body=nil,formdata=nil}

参数说明

baseurl:表示资源加载的相对路径

body:发送 POST 请求时的表单数据,使用的Content-type为application/json

foromdata:发送 POST 请求时的表单数据,使用的Content-type为application/x-www-form-urlencoded

该方法返回的结果是结果 ok 和原因 reason 的组合,如果 ok 为空,代表网页的加载出现了错误,此时 reason 变量中包含了错误原因,否则页面加载成功

function main(splash,args)
	local ok,reason=splash:go("http://httpbin.org/post",http_method='POST',body="name=YS")
    if ok then
    	return splash:html()
    end
end
wait()

控制页面的等待时间

ok,reason = splash:wait{time,cancel_on_redirect=false,cancen_on_error=true}

参数说明

cancel_on_redirect:可选参数,默认为false,表示如果发生了重定向就停止等待并返回重定向结果

cancel_on_error:可选参数,默认为false,表示如果发生了加载错误就停止等待

返回结果同样是结果 ok 和原因 reason 的组合

jsfunc()

此方法可以直接调用 JS 定义的方法,但是所调用的方法需要用双中括号包围,相当于实现了 JS 方法到 Lua 脚本的转化

function main(splash,args)
	local get_div_count = splash:jsfunc([[
    function(){
        var body = document.body;
        var divs = body.getElementByTagName('div');
        return divs.length
    }    
    ]])
    splash:go('https://www.baidu.com')
    return ('There are %S DIVS'):format(get_div_count())
evaljs()

此方法可以执行 JS 代码并返回最后一条 JS 语句的返回结果

result = splash:evaljs(js)

比如可以用 local title = splash:evaljs(‘document.title’) 来获取页面标题

runjs()

此方法可以执行 JS 代码,与 evaljs 类似,但更偏向于执行某些特定动作或声明某些方法

function main(splash,args)
    splash:go('http://www.baidu.com')
    splash:runjs("foo=function(){reyirm 'bar'}")
    local result = splash:evaljs('foo()')
    return result
end
autoload()

此方法可设置每个页面访问时自动加载的对象

ok,reason = splash:autload(source_or_url,source=nil,url=nil)

参数说明

source_or_url:JS 代码或 JS 库链接

source:JS 代码

url:JS 库链接

此方法只负责加载 JS 代码或库,不执行任何操作,如果要执行操作可以调用 evaljs()和runjs()

function main(splash,args)
    splash:autoload([[
        function get_document_title(){
        return document.title;
    }
        ]])
    splash:go('https://www.baidu.com')
    return splash:evaljs('get_document_title()')
end

另外也可以加载某些方法库,如 jQuery

function main(splash,args)
    assert(splash:autoload('https://code.jquery.com/jquert-2.1.3.min.js'))
    assert(splash:go('https://www.taobao.com'))
    local version = splash:evaljs('$.fn.jquery')
    return 'JQuery version:'.. version
call_later()

此方法可以通过设置定时任务和延时时间来实现任务延长执行,并且可以再执行前通过 cancel() 方法重新执行定时任务

function main(splash,args)
    local snapshots={}
    local timer=splash:call_later(function()
            snapshots['a'] = splash:png()
            splash:wait(1.0)
            snapshots['b'] = splash:png()
    	end,0.2)
    splash:go('https://www.taobao.com')
    splash:wait(3.0)
    return snapshots
end
http_get()

此方法可以模拟发送 HTTP 的 GET 请求

response = splash:http_get{url,headers=nil,follow_redirects = true}

follow_redirects:可选参数,表示是否启动自动重定向

http_post()

response = splash:http_post{url,headers=nil,follow_redirects = true,body=nil}

body:可选参数,即表单数据,默认为空

set_content()

此方法用来设置页面的内容

function main(splash)
    assert(splash:set_content('

hello

'
)) end
html()

用来获取网页源代码

function main(splash,args)
    splash:go('https://httpbin.org/get')
    return splash:html()
end
png()

此方法用来获取PNG格式的网页截图

jpeg()

用来获取JPEG格式的网页截图

har()

用来获取页面加载的过程描述

url()

获取当前正在访问的 URL

get_cookies()

获取当前页面的Cookies

add_cookie()

为当前页面添加cookie

clear_cookies()

此方法可以清除所有的Cookies

get_viewport_size()

获取当前浏览器页面的大小,即宽高

set_viewport_size()

设置当前浏览器页面的大小,即宽高

set_viewport_full()

设置浏览器全屏展示

set_user_agent()

设置浏览器的User-Agent

set_custom_headers()

设置请求头

function main(splash)
    splash:set_custom_headers({
            ['User-Agent']='Splash'
            ['Site']='Splash'
        })
    splash:go('http://httpbin.org/get')
    return splash.html()
end
select()

该方法可以选中符合条件的第一个节点,如果有多个节点符合条件,只返回一个,参数为CSS选择器

function main(splash)
    splash:go('https://www.baidu.com')
    input=splash:select('#kw')
    input:send_text('Splash')
    splash.wait(3)
    return splash:png()
end
select_all()

选中符合条件的所有节点

function main(splash)
    local treat = require('treat')
    assert(splash:go('http://quotes.toscrape.com/'))
    assert(splash:wait(0.5))
    local texts = splash:select_all('.quote.text')
    local results = {}
    for index,text in ipairs(tetxs) do
        results[index] = text.node.innerHTML
    end
    return treat.as_array(results)
end
mouse_click()

此方法可以模拟鼠标点击操作,传入的参数为坐标值 x 和 y ,此外也可以直接选中某个节点,然后调用此方法

function main(splash)
    splash:go('https://www.baidu.com/')
    input = splash:select('#kw')
    input:send_text('Splash')
    submit = splash:select('#su')
    submin:mouse_click()
    splash:wait(3)
    return splash:png()
end

2.3.Splash API 调用

前面说明了 Splash Lua 脚本的用法,Splash 给我们提供了一些 HTTP API 接口,我们只需要传递相应参数即可

render.html

此接口用于获取 JS 渲染的页面的 HTML 代码,接口地址为 Splash 的运行地址加此接口名称,给此接口传递 url 参数来指定渲染的 URL ,返回结果即是渲染后的源代码

import requests
url = 'http://localhost:8050/render.html?url=https://www.baidu.com&wait=5'
response=requests.get(url)
print(response.text)

另外,此接口还支持代理设置,图片加载设置,Headers 设置,请求方法设置

render.png

返回页面截图的 PNG 格式二进制数据,需要的参数比render.html多了几个,如需要设置页面宽高

render.jpeg

返回页面截图的 JPEG 格式二进制数据,比 render.png 多了 quality 参数,用来设置图片质量

render.har

返回页面加载的 HAR 数据,是一个 JSON 格式的数据

render.json

此接口包含了前面接口的所有功能,返回结果是 JSON 格式

可以传入不同的参数控制其返回结果,比如传入 html = 1,返回结果增加网页源代码数据,传入 png = 1,返回数据增加了页面的 PNG 截图数据

execute

此接口是最强大的接口,可以实现 Lua 脚本的对接,可以于网页交互

import requests
from urllib.parse import quote
lua = '''
function main(splash)
	return 'hello'
end
'''
url = 'http://localhost:8050/execute?lua_source='+quote(lua)
response = requests.get(url)
print(response.text)
#hello

Lua 脚本用三引号包括起来,quote()将脚本进行 URL 转码,构造 Splash请求 URL,将其作为lua_source 参数传递,这样运行结果会显示 Lua 脚本执行后的结果

import requests
from urllib.parse import quote
lua = '''
function main(splash)
	local treat = require('treat')
	local response = splash:http_get('http://httpbin.org/get')
	return {
	html = treat.as_string(response.body),
	url = response.url,
	status = response.status
	}
end
'''
url = 'http://localhost:8050/execute?lua_source='+quote(lua)
response = requests.get(url)
print(response.text)
'''
{"html": "{\n  \"args\": {}, \n  \"headers\": {\n    \"Accept-Encoding\": \"gzip, deflate\", \n    \"Accept-Language\": \"en,*\", \n    \"Host\": \"httpbin.org\", \n    \"User-Agent\": \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/602.1 (KHTML, like Gecko) splash Version/10.0 Safari/602.1\", \n    \"X-Amzn-Trace-Id\": \"Root=1-602a82d9-7b311490670a6a0521213ed7\"\n  }, \n  \"origin\": \"183.18244\", \n  \"url\": \"http://httpbin.org/get\"\n}\n", "status": 200, "url": "http://httpbin.org/get"}
'''

如此一来,之前的 Lua 脚本均可用此方法与 Python 进行对接,所有网页的动态渲染,模拟点击,表单提交,页面滑动,延时等待均可自由控制

3.Splash 负载均衡配置

在用 Splash 做页面抓取时,如果爬取的量非常大,用一个 Splash 服务来处理压力太大,此时可以考虑搭建一个负载均衡器来把压力分散到各个服务器上,相当于多个机器多个服务共同参与任务的处理,可以减小单个 Splash 服务的压力

七、验证码的识别

1.图形验证码

import pytesseract
from PIL import Image
Image = Image.open('C:/Users/PC/Desktop/CheckCode.jpg')
Image = Image.convert('L')
r = pytesseract.image_to_string(Image)
Image.show()  #看到处理之后的图片
print(r)  #kYnS

识别率较低,需要经过训练

2.极验滑动验证码

极验滑动验证码需要拖动拼合滑块才能完成验证,可以使用 Selenium 来实现

极验验证码官网为 http://www.geetest.com/

极验验证码较图形验证码识别难度更大,有一种情况为先点击按钮进行智能验证,如果验证不通过,则会弹出滑动验证的窗口,拖动滑块拼合图像进行验证,之后三个加密参数会生成,通过表单提交到后台,后台还会进行一次验证

在这里插入图片描述

识别思路

对于智能验证按钮,一般同一个会话第二次点击会直接通过验证,如果智能识别不通过,会弹出滑动验证窗口,需要拖动滑块完成二步验证,接下来就可以提交表单了

模拟点击可以用 Selenium 直接实现,滑动拼接待学 dog~

3.点触验证码

点触验证码也是一种应用广泛的验证码,同样可以使用 Selenium 实现,但识别难度也较大,可以通过网络平台提交识别 dog~

八、代理的使用

1.在在请求模块中使用

from urllib.error import URLError
from urllib.request import ProxyHandler,build_opener
import requests
proxy='185.38.111.1:8080'
#需要认证在代练前加上用户名和密码
#proxy='username:[email protected]:8080'
proxies={
    'http':'http://'+proxy,
    'https':'https://'+proxy
    }
try:
    response=requests.get('http://httpbin.org/get',proxies=proxies,timeout=10)
    print(response.content.decode('utf-8'))
except:
    print('出错')

2.代理池的维护

代理需要提前筛选,将不可用的剔除掉,保留可用代理,

九、Scrapy

1.文件爬虫

1.1.定义文件字段

class MatplotlibExamplesItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    
    file_urls=scrapy.Field()
    files=scrapy.Field()

1.2.修改管道文件

1.2.1.文件名在文件的url中

修改FilesPipeline的文件命名规则,实现文件内容命名,同时分在不同文件夹

from scrapy.pipelines.files import FilesPipeline
from urllib.parse import urlparse
from  os.path import basename,dirname,join
class MyFilesPipeline(FilesPipeline):
    def file_path(self, request, response=None, info=None):
        path = urlparse(request.url).path   #eg_path=axes_grid//demo_axes_grid.py
        return join(basename(dirname(path)),basename(path))#以路径目录创建文件夹下载
1.2.2.文件名自定义
import scrapy
import re
from scrapy.pipelines.images import ImagesPipeline
from scrapy.exceptions import DropItem
from os.path import join
class NewSoimagesPipeline:
    def process_item(self, item, spider):
        return item
class MyFilesPipeline(ImagesPipeline):
    def get_media_requests(self,item,info):
        image_url=item['image_url']
        yield scrapy.Request(image_url,meta={'name':item['image_name']})     
    '''def item_completed(self,results,item,info):
        image_paths=[x['path'] for ok,x in results if ok]
        print('item_completed已经被执行')
        if not image_paths:
            raise DropItem('Item contains no images')
        return item'''
    def file_path(self,request,response=None,info=None):
        name=request.meta['name']
        filename=name+'.jpg'
        return join('C:\\Users\\PC\\Desktop\\Python\\爬虫练习\\new_soimages\\new_soimages\\images',filename)

1.3.启动配置,指定下载路径

ITEM_PIPELINES = {
    '(#projectname).pipelines.MyFilesPipeline':1
}
FILES_STORE='examples_src'
#IMAGES_STORE='image'

1.4.实现爬虫

1.4.1.链接提取类
import scrapy
from scrapy.linkextractors import LinkExtractor
from ..items import MatplotlibExamplesItem
class ExamplesSpider(scrapy.Spider):
    name = 'examples'
    #allowed_domains = ['matplotlib.org']
    start_urls = ['http://matplotlib.org/examples/index.html']
    #提取详情页面链接并提交
    def parse(self, response):
        le=LinkExtractor(restrict_xpaths='//li[@class="toctree-l1"]/ul/li[@class="toctree-l2"]')
        links=le.extract_links(response)
        for link in links:
            yield scrapy.Request(link.url,callback=self.parse_examples)
    #提取下载url并提交
    def parse_examples(self,response):
        href=response.xpath('//a[@class="reference external"]/@href').extract_first()
        url=response.urljoin(href)
        example=MatplotlibExamplesItem()
        example['file_urls']=[url]
        return example
1.4.2.链接构造类
import scrapy
import json
from ..items import NewSoimagesItem
class ImageSpider(scrapy.Spider):
    name = 'image'
    #allowed_domains = ['none']
    BASE_URL = 'https://image.so.com/zjl?ch=art&sn={}&listtype=new&temp=1'
    start_index = 0
    MAX_DOWNLOAD_NUM = 100 #下载数量约为 (MAX_DOWNLOAD_NUM/一页的数量+1)*一页的数量
    start_urls = [BASE_URL.format(0)]
    def parse(self, response):
        infos=json.loads(response.body.decode('utf-8'))
        #print(infos)
        item=NewSoimagesItem()
        for info in infos['list']:
            item['image_url']=info['qhimg_url']
            item['image_name']=info['pic_desc']
            yield item
        self.start_index=self.start_index+infos['count']
        if infos['count']>0 and self.start_index<self.MAX_DOWNLOAD_NUM:
            yield scrapy.Request(self.BASE_URL.format(self.start_index),callback=self.parse)

1.5.统一缩进方式

2.模拟登录

2.1.使用FormRequest(可能不可行)

import scrapy
from scrapy.http import Request,FormRequest
class LoginSpider(scrapy.Spider):
    name='login'
    allowed_domains='www.yaozh.com'
    start_urls=['https://www.yaozh.com']
    login_url='https://www.yaozh.com/login/'
    def parse(self,response):
        name=response.xpath('/html/body/div[1]/div/div[2]/a[1]/text()')
        yield name
    def start_reqeusts(self):
        yield Request(self.login_url,callback=self.login)
    def login(self,response):
        fd={'username':'15525810502','password':'86vlwug1'}
        yield FormRequest.from_response(response,formdata=fd,callback=self.parse_login)
    def parse_login(self,response):
        if 'desktop landscape js rgba borderimage borderradius boxshadow textshadow opacity placeholder no-iframe' in response.text:
            print('登录成功')
            yield from super().start_requests()
        else:
            print('登录失败')
    

2.2.使用在Request添加cookies参数

import re
import scrapy
class LoginTestSpider(scrapy.Spider):
    name = 'login_test'
    allowed_domains = ['www.yaozh.com']
    start_urls = ['https://www.yaozh.com/member/']
    def start_requests(self):
        cookies='think_language=zh-CN; Hm_lvt_65968db3ac154c3089d7f9a4cbb98c94=1612775691; _ga=GA1.2.87747901612775691; _gid=GA1.2.1940605412.1612775691; _gat=1; Hm_lpvt_65968db3ac154c3089d7f9a4cbb98c94=1612778959; yaozh_userId=1034779; UtzD_f52b_saltkey=r8rUC8oU; UtzD_f52b_lastvisit=1612775378; yaozh_uidhas=1; acw_tc=2f624a2c16127789259967583e05f17b4777229366283295014080103242da; UtzD_f52b_ulastactivity=1611138183%7C0; _ga=GA1.1.1686181705.1612778990; _gid=GA1.1.431866152.1612778990; yaozh_logintime=1612840644; yaozh_user=1034779%09YS12345; yaozh_jobstatus=kptta67UcJieW6zKnFSe2JyYnoaSZ5hpnJueg26qb21rg66flM6bh5%2BscZhyVLy4k5OXmJZZoKifnZ%2BDn5iorJDVop6Yg3HYnmpnm1pjmZa00Ae2c26e315ea124717CC60390509fFUmZebmWueV6DXn5VtWamhnsZbbKabZ5ieW2iXaWSbmZKXmJmUZ5VXoOE%3Df83eeaa91a70f1c66e3f4e55f8c3e222; db_w_auth=851571%09YS12345; UtzD_f52b_lastact=1612840646%09uc.php%09; UtzD_f52b_auth=b2bcvD2LRsEV6wnPyCO3tguMVjskJED3%2FO2sykkP9mUai1qU9Cc%2F2sFM1ui3OdD62G2Z6pMkSelfcK5INGEu8EIAkWc; yaozh_mylogin=1612840648; PHPSESSID=n10239pmii120ncrdkdgtq0a85'

        cookie_list=[cookie for cookie in cookies.split(';')]
        #print(cookie_list)
        try:
            cookie_list.remove('')
        except:
            pass
        cookies={cookie.split('=')[0]:cookie.split('=')[1] for cookie in cookie_list}
        #print(cookies)
        yield scrapy.Request(self.start_urls[0],cookies=cookies,callback=self.parse)
    def parse(self, response):
        print('开始查找')
        p=re.compile('0.00')
        print(p.findall(response.body.decode('utf-8')))

3.爬取动态网页

3.1.使用render.html进行渲染(无触发事件型)
3.1.1.开启Splash渲染服务
3.1.2.发送SplashRequest请求
import scrapy
from scrapy_splash import SplashRequest

class QuotesSpider(scrapy.Spider):
    name = 'quotes'
    #allowed_domains = ['quotes.toscrape.com']
    start_urls = ['http://quotes.toscrape.com/js']
    def start_requests(self):
        #请求第一页
        for url in self.start_urls:
            #调用SplashRequest渲染
            yield SplashRequest(url,args={'images':0,'timeout':3})


    def parse(self, response):
        #获取名言和作者
        for sel in response.xpath('//div[@class="quote"]'):
            quote=sel.xpath('./span[@class="text"]/text()').extract_first()
            author=sel.xpath('./span/small[@class="author"]/text()').extract_first()
            
            yield {'quote':quote,'author':author}

        #获取下一页路径
        href=response.xpath('//li[@class="next"]/a/@href').extract_first()
        if href:
            url=response.urljoin(href)
            yield SplashRequest(url,args={'image':0,'timeout':3},callback=self.parse)
3.1.3.修改配置文件
#设置渲染服务地址
SPLASH_URL='http://localhost:8050'
#设置去重过滤
DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'
#用来支持cache_args(用于缓存每次都需要设置的args属性),
SPIDER_MIDDLEWARES = {
    #'spalsh_example.middlewares.SpalshExampleSpiderMiddleware': 543,
    'scrapy_splash.SplashDeduplicateArgsMiddleware':100,
}
DOWNLOADER_MIDDLEWARES = {
    #'spalsh_example.middlewares.SpalshExampleDownloaderMiddleware': 543,
    'scrapy_splash.SplashCookiesMiddleware':723,
    'scrapy_splash.SplashMiddleware':725,
    'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware':810
}
3.2.使用execute.html渲染服务(触发JS代码)
3.2.1.开启Splash渲染服务
3.2.2.指定爬取范围,先触发JS代码返回HTML页面再进行爬取
import scrapy
from scrapy import Request
from scrapy_splash import SplashRequest
import json

# lua语言
lua_script='''
--定义一个函数,参数是splash                   
function main(splash) 
    -- 打开url页面,这里指打开 splash.args.url 就相当于加载时配置的URL参数(即SPLASH_URL = 'http://localhost:8050')
    splash:go(splash.args.url) 
    -- 等待页面渲染2秒
    splash:wait(2)
    -- 执行JavaScript语句(无返回值)滚动浏览器到访问 HTML 元素中的 "calass=page",意思是滚动浏览器到底端(page位置)。
    splash:runjs("document.getElementsByClassName('page')[0].scrollIntoView(true)")
    -- 等待页面渲染2秒
    splash:wait(2)
    -- 返回获取当前页面的 html 文本
    return splash:html()
end
'''

class JdBookSpider(scrapy.Spider):
    name = 'jd_book'
    allowed_domains = ['search.jd.com']
    # start_urls = ['http://search.jd.com/']
    base_url = 'https://search.jd.com/Search?keyword=python&enc=utf-8&wq=python' # 起始页

    def start_requests(self):
        # 请求第一页, 无需 JS 渲染
        yield Request(self.base_url, callback=self.parse_urls, dont_filter=True)

    def parse_urls(self,response):
        # 获取商品总数,计算出总页数
        #total = int(response.css('span#J_resCount::text').extract_first())
        #total = response.css('span#J_resCount::text').re_first('\d+.+\d')
        #total = int(float(total)*10000)
        #pageNum = total // 60 + (1 if total % 60 else 0 )

        # 测试时可以不用取那么多页
        pageNum = 4

        # 构造每页的url,向 Splash 的 execute 端点发送请求
        for i in range(pageNum):
            url = '%s&page=%s' % (self.base_url, 2 * i + 1)
            # print(url)
            yield SplashRequest(url,
                                endpoint='execute',
                                args={'lua_source':lua_script},
                                cache_args=['lua_source'])

    def parse(self, response):
        # 获取每一个页面中每本书的名字和价格
        for sel in response.css('ul.gl-warp.clearfix > li.gl-item'):
            yield {
                'name':sel.css('div.p-name').xpath('string(.//em)').extract_first(),
                'pric':sel.css('div.p-price i::text').extract_first(),
            }
3.2.3.修改配置
SPLASH_URL = 'http://localhost:8050'
USER_AGENT='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36'
SPIDER_MIDDLEWARES = {
    'jd.middlewares.JdSpiderMiddleware': 543,
    'scrapy_splash.SplashDeduplicateArgsMiddleware':100,
}
DOWNLOADER_MIDDLEWARES = {
    'jd.middlewares.JdDownloaderMiddleware': 543,
    'scrapy_splash.SplashCookiesMiddleware':723,
    'scrapy_splash.SplashMiddleware':725,
    'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware':810
}
3.3.点击加载更多
3.3.1.在抓取页面抓包Network,寻找 Type 为 xhr ,initiator 为 jquery.js:3 的 url
https://movie.douban.com/j/search_subjects?type=movie&tag=%E8%B1%86%E7%93%A3%E9%AB%98%E5%88%86&sort=recommend&page_limit=20&page_start=0
https://movie.douban.com/j/search_subjects?type=movie&tag=%E8%B1%86%E7%93%A3%E9%AB%98%E5%88%86&sort=recommend&page_limit=20&page_start=20
3.3.2.定义字段
3.3.3.启动配置
USER_AGENT='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36'
ROBOTSTXT_OBEY = False
DOWNLOAD_DELAY = 3
DOWNLOADER_MIDDLEWARES = {
    'movie.middlewares.MovieDownloaderMiddleware': 543,
}
3.3.4.模拟发送 jQuery 请求 (见 一.4.1 链接构造类)

4.存入数据库

4.1.存入MongoDB

4.1.1.定义字段
class BookItem(scrapy.Item):
    # 使用Field()元数据封装数据
    name = scrapy.Field() # 书名
    price = scrapy.Field() # 价格
    review_rating = scrapy.Field() # 评价等级(1~5 星)
    review_num = scrapy.Field() # 评价数量
    upc = scrapy.Field() # 产品编码
    stock = scrapy.Field() # 库存数量
    # 当存储在 mongodb 数据库中时,需要加上这么一句
    _id = scrapy.Field()
4.1.2.修改管道文件
#实现数据写入到 MongoDB 数据库中
# 在 pipleines.py 中实现 MongoDBPipeline 代码如下:

from pymongo import MongoClient
class MongoDBPipeline:
    # open_spider 方法在开始爬取数据之前被调用,在该方法中通过spider.settings 对象读取用户在配置文件中的指定的数据库,
    # 然后建立与数据库连接,将得到的 MongoClient 对象和 Database 对象,分别赋值给 self.db_client 和 self.db 以便后用
    def open_spider(self,spider):
        db_uri = spider.settings.get('MONGODB_URI','mongodb://localhost:27017')
        db_name = spider.settings.get('MONGODB_DB_NAME','scrapy_default')

        self.db_client = MongoClient('mongodb://localhost:27017')
        self.db = self.db_client[db_name]

    # close_spider 方法,爬取数据后,关闭连接
    def close_spider(self,spider):
        self.db_client.close()

    # process_item 方法处理爬取到的每一项数据,在该方法中调用 insert_db 方法,执行数据库的插入操作。
    # 在 insert_db 方法中,先将一项数据转成字典,然后调用 insert_one 方法将其插入集合 books。
    def process_item(self,item,spider):
        self.insert_db(item)
        return item

    def insert_db(self,item):
        #  isinstance 类型判断函数 (判断 item 是否是item对象类型)
        if isinstance(item,type(item)):
            item = dict(item)

        self.db.books.insert_one(item)
from scrapy.exceptions import DropItem

class DuplicatesPipline(object):
    def __init__(self):
        self.book_set = set()

    def process_item(self, item, spider):
        # 取出name
        name = item['name']
        # 判断name是否在book_set中
        if name in self.book_set:
            raise DropItem("Duplicat book found: %s" % item)
        else:
            self.book_set.add(name)
            return item
4.1.3.启动配置
# Mongodb 数据库信息设置 2020-03-24
MONGODB_URI = 'mongodb://localhost:27017'
MONGODB_DB_NAME = 'scrapy_db'
ITEM_PIPELINES = {
    #'toscrape_book.pipelines.ToscrapeBookPipeline': 300,
    'toscrape_book.pipelines.BookPipeline': 310,			#数字转化
    'toscrape_book.pipelines.DuplicatesPipline': 300,		#去重
    # 把数据存储在 mongodb 数据库中 2020-03-24
     'toscrape_book.pipelines.MongoDBPipeline':403,
}
4.1.4.实现爬虫(见4.3.3)

4.2.存入Redis

4.2.1.启动Redis-sever服务
4.2.2.定义字段
class BookItem(scrapy.Item):
    # 使用Field()元数据封装数据
    name = scrapy.Field() # 书名
    price = scrapy.Field() # 价格
    review_rating = scrapy.Field() # 评价等级(1~5 星)
    review_num = scrapy.Field() # 评价数量
    upc = scrapy.Field() # 产品编码
    stock = scrapy.Field() # 库存数量
4.2.3.启动配置
ITEM_PIPELINES = {
    #'toscrape_book.pipelines.ToscrapeBookPipeline': 300,
    'toscrape_book.pipelines.BookPipeline': 310,			#数字转化
    'toscrape_book.pipelines.DuplicatesPipline': 300,		#去重
    'toscrape_book.pipelines.RedisPipeline':404,
}
4.2.4.实现爬虫(见4.3.3)

4.3.存入自定义自定义导出(以实现Excel导出为例,Excel导出Scrapy未定义)

4.3.1.在settings.py文件同级目录下实现my_exporters
# 以excel格式导出的Exporter
from scrapy.exporters import BaseItemExporter
import xlwt

class ExcelItemExporter(BaseItemExporter):
    def __init__(self, file, **kwargs):
        self._configure(kwargs)
        self.file = file
        self.wbook = xlwt.Workbook()
        self.wsheet = self.wbook.add_sheet('scrapy')
        self.row = 0

    def finish_exporting(self):
        self.wbook.save(self.file)

    def export_item(self, item):
        fields = self._get_serialized_fields(item)
        for col, v in enumerate(x for _,x in fields):
            self.wsheet.write(self.row,col,v)
        self.row += 1
4.3.2.启动配置
# 添加新的导出数据格式
FEED_EXPORTERS = {'excel':'toscrape_book.my_exporters.ExcelItemExporter'}
4.3.3.实现爬虫
import scrapy
from scrapy.linkextractors import LinkExtractor
from ..items import BookItem

class BooksSpider(scrapy.Spider):
    # 爬虫名
    name = 'books'
    allowed_domains = ['books.toscrape.com']
    # 抓取网点起点
    start_urls = ['http://books.toscrape.com/']

    # 书籍列表页面的解析函数
    def parse(self, response):
        # 提取书籍列表页面中每本书的链接 (LinkExtractor提取链接的用法)
        le = LinkExtractor(restrict_css='article.product_pod h3')
        links = le.extract_links(response)
        # print('links_list_url:',links)
        for link in links :  # le.extract_links(response):
            yield scrapy.Request(link.url, callback=self.parse_book)

        # 提取下一页(LinkExtractor提取链接的用法)
        le = LinkExtractor(restrict_css='ul.pager li.next')
        links = le.extract_links(response)
        # print('links_next_url:',links)
        if links:
            next_url = links[0].url
            yield scrapy.Request(next_url, callback=self.parse)

    #书籍页面的解析函数:
    def parse_book(self,response):
        # 元数据保存先在item.py定义,再引用
        book = BookItem()
        sel = response.css('div.product_main')
        book['name'] = sel.css('h1::text').extract_first()
        book['price'] = sel.css('p.price_color::text').extract_first()
        book['review_rating'] = sel.css('p.star-rating::attr(class)').re_first('star-rating ([A-Za-z]+)')
        book['stock'] = sel.css('p.instock::text').re_first('stock \((.*?) available\)')

        sel = response.css('table.table-striped')
        book['upc'] = sel.css('tr:nth-child(1)>td::text').extract_first()
        book['review_num'] = sel.css('tr:nth-last-child(1)>td::text').extract_first()

        yield book

xporter):
def init(self, file, **kwargs):
self._configure(kwargs)
self.file = file
self.wbook = xlwt.Workbook()
self.wsheet = self.wbook.add_sheet(‘scrapy’)
self.row = 0

def finish_exporting(self):
    self.wbook.save(self.file)

def export_item(self, item):
    fields = self._get_serialized_fields(item)
    for col, v in enumerate(x for _,x in fields):
        self.wsheet.write(self.row,col,v)
    self.row += 1

#### 4.3.2.启动配置

```python
# 添加新的导出数据格式
FEED_EXPORTERS = {'excel':'toscrape_book.my_exporters.ExcelItemExporter'}
4.3.3.实现爬虫
import scrapy
from scrapy.linkextractors import LinkExtractor
from ..items import BookItem

class BooksSpider(scrapy.Spider):
    # 爬虫名
    name = 'books'
    allowed_domains = ['books.toscrape.com']
    # 抓取网点起点
    start_urls = ['http://books.toscrape.com/']

    # 书籍列表页面的解析函数
    def parse(self, response):
        # 提取书籍列表页面中每本书的链接 (LinkExtractor提取链接的用法)
        le = LinkExtractor(restrict_css='article.product_pod h3')
        links = le.extract_links(response)
        # print('links_list_url:',links)
        for link in links :  # le.extract_links(response):
            yield scrapy.Request(link.url, callback=self.parse_book)

        # 提取下一页(LinkExtractor提取链接的用法)
        le = LinkExtractor(restrict_css='ul.pager li.next')
        links = le.extract_links(response)
        # print('links_next_url:',links)
        if links:
            next_url = links[0].url
            yield scrapy.Request(next_url, callback=self.parse)

    #书籍页面的解析函数:
    def parse_book(self,response):
        # 元数据保存先在item.py定义,再引用
        book = BookItem()
        sel = response.css('div.product_main')
        book['name'] = sel.css('h1::text').extract_first()
        book['price'] = sel.css('p.price_color::text').extract_first()
        book['review_rating'] = sel.css('p.star-rating::attr(class)').re_first('star-rating ([A-Za-z]+)')
        book['stock'] = sel.css('p.instock::text').re_first('stock \((.*?) available\)')

        sel = response.css('table.table-striped')
        book['upc'] = sel.css('tr:nth-child(1)>td::text').extract_first()
        book['review_num'] = sel.css('tr:nth-last-child(1)>td::text').extract_first()

        yield book

你可能感兴趣的:(杂七杂八,爬虫,http,python,json,html5)