爬虫-大数据时代的必备技能

Python 爬虫

一、爬虫五个步骤

1.需求分析,确定url

2.发起请求

3.获取响应内容 (urllib, requests)

4.解析数据(re, XPATH, jsonpath, bs(css selector))

5.储存数据(MySQL)


二、HTML概述和基本结构

1. 网页的组成

  • 网页由三部分组成

    html: 结构层,主要负责页面的结构
    css:  表现层,主要负责网页的样式
    javascrapy: 行为层,主要负责用户的动态交互
    

2. HTML基本结构

HTML是由:标签和内容构成

<title>这是文档中的标题</title>

HTML标签(标记)的语法:

标签是由”<”和”>”括起来

双标签:<标签名>....

单标签:<标签名>

<!DOCTYPE html>
<!-- 这是一段注释  -->
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>网页标题</title>
</head>
<body>
    网页显示内容
</body>
</html>

3. HTML常用标签

  • 标题标签

    h1~h6
    
  • 段落标签,换行符,实体字符

    段落标签p
    换行符 <br>
    实体字符 &lt; &gt;
    
  • 块标签:

    div 没有默认样式 一般配合css使用做网页的布局
    
  • 图片标签

    <img src="">
    src 是属性 用于连接图片的地址
    
  • 音频标签

    <audio src=""></audio>
    
  • 视频标签

    &lt;video src=&quot;&quot;&gt;&lt;/video&gt;
    
  • 链接标签

    <a href="">内容</a>
    href属性 跳转地址
    
  • 表格标签

    table 声明一个表格
    tr 行
    td 列
      
    属性:
          1、border 定义表格的边框
      
          2、cellpadding 定义单元格内内容与边框的距离
      
          3、cellspacing 定义单元格与单元格之间的距离
      
          4、align 设置单元格中内容的水平对齐方式,设置值有:left | center | right
      
          5、valign 设置单元格中内容的垂直对齐方式 top | middle | bottom
      
          6、colspan 设置单元格水平合并
      
          7、rowspan 设置单元格垂直合并
    
  • 列表:

    ul>li 无须列表
    ol>li 有序列表
    dl>dt>dd 自定义列表
    
  • 表单:

    from 声明一个表单域
       属性: action  提交地址
             method   请求方式
      
    <form action="http://www..." method="get">
      
    ...
      
    </form>
    

4. HTML节点树

节点树中,顶端节点为根节点,除了根节点每个节点都有父节点,同时也拥有多个子节点和兄弟节点


三、网络简介

1. 网络模型:

实际应用四层模型 理论七层网络模型:
链路层/网络接口层 物理层
网络层 数据链路层
传输层 网络层
应用层 传输层
  会话层
  表示层
  应用层

2. IP地址

地址是用来标记地点的

每一个IP地址都包括两部分:网络地址和主机地址

  • A类IP地址

    一个A类地址由1字节的网络地址和3字节主机地址组成,
    网络地址最高位必须是0
    地址范围1.0.0.1-126.255.255.254
    可用的A类网络有126个,每个网络能容纳1677214个主机
    
  • B类IP地址

    一个B类地址由2个字节的网络地址和2个字节的主机地址组成,
    网络地址最高位必须是01
    地址范围128.1.0.1-191.255.255.254
    可用的B类网络有16384个,每个网络能容纳65534个主机
    
  • C类IP地址

    一个C类地址由3个字节的网络地址和1个字节的主机地址组成,
    网络地址最高位必须是110
    地址范围192.0.0.1-223.255.255.254
    可用网络有2097152个,每个网络能容纳254个主机
    我们就是ip221---
    
  • D类IP地址多用于多点广播

    D类IP地址第一个字节由1110开始,他是专门保留的地址。
    他并不指向特定的网络,目前这一类地址备用在多点广播中
    多点广播地址用来一次寻址一组计算机
    地址范围224.0.0.1-239.255.255.254
    
  • E类IP地址

    以1111开始,为将来使用保留
    E类地址保留,仅实验和开发用
    
  • 注意

    IP地址127.0.0.1-127.255.255.255用于回路测试,如:127.0.0.1可以代表本机IP,
    用http://127.0.0.1就可以测试本机配置的WEB服务
    

3. 端口

  • 端口号

    端口号是通过端口来标记的,范围是从0-65535

  • 分类

    不是随意使用的,而是按照一定的规定进行分配的

    端口号的分类有好几种,我们只介绍知名端口好和动态端口号

知名端口

  知名端口是众所周知的端口号,一般是从0-1023
  80端口分配给HTTP服务
  21端口分配给FTP服务

动态端口

  动态端口的范围一般是从1024到65535
  之所以称之为动态端口,是因为它一般不固定分配某种服务,而是动态分配。
  动态分配是指当一个系统进程或者应用程序进程需要网络通讯时,它向主机申请一个端口,主机从可用的端口号中分配一个供它使用
  当这个进程关闭时,同事也就释放了所占用的端口号

4. UDP协议

  • UDP:

    用户数据报协议,不可靠性,

    只是把应用程序传给IP层数据报送出去,不能保证他们是否能到达目的地,

    传输数据报前不用在客户端和服务器之间建立连接,

    并且没有超时重发机制,所以传输速度快。

  • 特点:

    安全性差,传输速度快,无序,大小有限制64kb

    socket 模块

服务器端套接字  
s.bind() 绑定地址(host,port)到套接字, 在AF_INET下,以元组(host,port)的形式表示地址。
s.listen() 开始TCP监听。backlog指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为1,大部分应用程序设为5就可以了。
s.accept() 被动接受TCP客户端连接,(阻塞式)等待连接的到来
客户端套接字  
s.connect() 主动初始化TCP服务器连接,。一般address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。
s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
公共用途的套接字函数  
s.recv() 接收TCP数据,数据以字符串形式返回,bufsize指定要接收的最大数据量。flag提供有关消息的其他信息,通常可以忽略。
s.send() 发送TCP数据,将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。
s.sendall() 完整发送TCP数据,完整发送TCP数据。将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。
s.recvfrom() 接收UDP数据,与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。
s.sendto() 发送UDP数据,将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。
s.close() 关闭套接字
s.getpeername() 返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。
s.getsockname() 返回套接字自己的地址。通常是一个元组(ipaddr,port)
s.setsockopt(level,optname,value) 设置给定套接字选项的值。
s.getsockopt(level,optname[.buflen]) 返回套接字选项的值。
s.settimeout(timeout) 设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect())
s.gettimeout() 返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。
s.fileno() 返回套接字的文件描述符。
s.setblocking(flag) 如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。
s.makefile() 创建一个与该套接字相关连的文件

socket–UDP网络通讯

  • 服务端:

    1.创建套接字对象
    2.绑定ip地址和端口号
    3.接受消息
    4.返回消息
    5.关闭套接字
      
    import socket
    # 创建套接字
    udp_s = socket.socked(socket.AF_INET,socket.SOCK_DGRAM)
    # 绑定ip地址
    udp_s.bind(('',8080))
    # 接受消息
    data,addr = udp_s.recvfrom(1024)
    print(data)
      
    # 关闭套接字
    udp_s.close()
      
    
  • 客户端:

    1.创建套接字对象
    2.发送消息
    3.关闭套接字对象
      
    import socekd
    # 创建套接字对象
    udp_c = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    # 发送消息
    udp_c.sendto('hello'.encode('utf-8'),('192.168.1.15',8080))
    # 关闭套接字
    udp_c.close()
      
    

5. TCP协议

  • TCP

    在通讯之前,一定要先建立相关链接,才能发送数据

  • 特点

    安全性高,稳定性好,有序 速度相对较慢

  • 建立连接 三次握手

    ACK:确认标志
    SYN:同步标志
    第一次握手:主机A发送位码为syn=1,随机产生seq number=1234567的数据包到服务器,主机B由SYN=1知道,A要求建立联机;
      
    第二次握手:主机B收到请求后要确认联机信息,向A发送ack number=(主机A的seq+1),syn=1,ack=1,随机产生seq=7654321的包
      
    第三次握手:主机A收到后检查ack number是否正确,即第一次发送的seq number+1,以及位码ack是否为1,若正确,主机A会再发送ack number=(主机B的seq+1),ack=1,主机B收到后确认seq值与ack=1则连接建立成功。
      
    完成三次握手,主机A与主机B开始传送数据。
      
    

    Socket–TCP网络通讯

  • 服务端:

    1.创建套接字对象
    2.绑定ip地址和端口号
    3.监听
    4.接受消息
    5.返回消息
    6.关闭套接字
      
    import socket
    tcp_s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    tcp_s.bind(('',8080))
    tcp_s.listen()
    s,addr = tcp_s.accept()
    data = s.recv(1024)
    print(data.decode('utf-8'))
    s.send('hello'.encode('utf-8))
    s.close()
    tcp_s.close()
      
    
  • 客户端:

    1.创建套接字对象
    2.创建连接
    3.发送消息
    4.关闭套接字对象
      
    import socket
    tcp_c = socket.socket(socket.AF_INET,socket.SOCK_STRAM)
    tcp_c.connect(('192.168.1.15',8080))
    tcp_c.send('hello'.encode('utf-8'))
    tcp_c.close()
      
    

6. HTTP简介

HTTP协议主要工作在客户端-服务端的架构上,浏览器作为HTTP客户端通过URL向HTTP服务端即 WEB服务器发送请求,服务器根据接受到的请求,向客户端发送相应信息

  • 超文本传输协议

    是一种按照URL指示,将超文本文档从一台主机(Web服务器)传输到另一台主机(浏览器)的应用层协议,以实现超链接的功能。

  • 超文本(Hyper Text)

    包含有超链接(Link)和各种多媒体元素标记(Markup)的文本。这些超文本文件彼此链接,形成网状(Web),因此又被称为网页(Web Page)。这些链接使用URL表示。最常见的超文本格式是超文本标记语言HTML。

  • URL

    URL即统一资源定位符(Uniform Resource Locator),用来唯一地标识万维网中的某一个文档。URL由协议、主机和端口(默认为80)以及文件名几部分构成。

    例:http://www.aspxfans.com:8080/news/index.asp?boardID=5ID=24618page=1#name
    一个完整的URL包括以下几个部分
    1.协议部分:该URL的协议部分为“http:”,这代表网页使用的是HTTP协议。在Internet中可以使用多种协议,如HTTP,FTP等等本例中使用的是HTTP协议。在"HTTP"后面的“//”为分隔符
      
    2.域名部分:该URL的域名部分为“www.aspxfans.com”。一个URL中,也可以使用IP地址作为域名使用
      
    3.端口部分:跟在域名后面的是端口,域名和端口之间使用“:”作为分隔符。端口不是一个URL必须的部分,如果省略端口部分,将采用默认端口
      
    4.虚拟目录部分:从域名后的第一个“/”开始到最后一个“/”为止,是虚拟目录部分。虚拟目录也不是一个URL必须的部分。本例中的虚拟目录是“/news/”
      
    5.文件名部分:从域名后的最后一个“/”开始到“?”为止,是文件名部分,如果没有“?”,则是从域名后的最后一个“/”开始到“#”为止,是文件部分,如果没有“?”和“#”,那么从域名后的最后一个“/”开始到结束,都是文件名部分。本例中的文件名是“index.asp”。文件名部分也不是一个URL必须的部分,如果省略该部分,则使用默认的文件名
      
    6.锚部分:从“#”开始到最后,都是锚部分。本例中的锚部分是“name”。锚部分也不是一个URL必须的部分
      
    7.参数部分:从“?”开始到“#”为止之间的部分为参数部分,又称搜索部分、查询部分。本例中的参数部分为“boardID=5ID=24618page=1”。参数可以允许有多个参数,参数与参数之间用“”作为分隔符。
      
    

7. HTTP原理

  • 请求响应模型

    在用户点击URL,浏览器和服务器会执行哪些动作

    • 1.浏览器分析超链接中的URL
    • 2.浏览器向DNS请求解析www.sxtyu.com的IP地址
    • 3.DNS将解析出的IP地址202.2.16.21返回浏览器
    • 4.浏览器与服务器建立TCP连接(80端口)
    • 5.浏览器请求文档:GET /index.html(发送HTTP请求)
    • 6.服务器给出响应,将文档 index.html发送给浏览器
    • 7.释放TCP连接
    • 8.浏览器显示index.html中的内容
  • 连接方式

    • 1.非持久性连接
    • 2.持久性连接
  • 无状态性

    是指同一个客户端(浏览器)第二次访问同一个Web服务器上的页面时,服务器无法知 道这个客户曾经访问过。HTTP的无状态性简化了服务器的设计,使其更容易支持大量并发的HTTP请求。

8. HTTP请求报文

即从客户端(浏览器)向Web服务器发送的请求报文。报文的所有字段都是ASCII码。

  • 请求报头中的方法

    • HEAD 类似GET请求,只不过返回的响应中没有具体内容,用于获取报头
    • PUT 从客户端向服务器传送的数据取代指定文档中的内容
    • DELETE 请求服务器删除指定的页面
    • CONNECT 把服务器当作跳板,让服务器代替客户端访问其他网页
    • OPTIONS 允许客户端查看服务器的性能
    • TRACE 回显服务器收到的请求,主要用于测试或诊断
  • 请求头部

    Accept:

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

    Accept-Language:

    指定客户端可接受的语言类型。

    Accept-Encoding:

    指定客户端可接受的内容编码。

    Host:

    用于指定请求资源的主机IP和端口号,其内容为请求URL的原始服务器或网关的位置。 从HTTP 1.1版本开始,请求必须包含此内容。

    Cookie:

    也常用复数形式 Cookies, 这是网站为了辨别用户进行会话跟踪而存储在用户本地的数据。 它的主要功能是维持当前访问会话。 例如,我们输入用户名和密码成功登录某个网站后,服务器会用会话保存登录状态信息 ,后面我们每次刷新或请求该站点的其他页面时, 会发现都是登录状态,这就是Cookies的功劳。 Cookies里有信息标识了我们所对应的服务器的会话, 每次浏览器在请求该站点的页面时,都会在请求头中加上Cookies并将其发送给服务器, 服务器通过Cookies识别出是我们自己,并且查出当前状态是登录状态, 所以返回结果就是登录之后才能看到的网页内容。

    Referer:

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

    User-Agent:

    简称UA,它是一个特殊的字符串头,可以使服务器识别客户使用的操作系统及版本、浏览器及版本等信息。 在做爬虫时加上此信息,可以伪装为浏览器;如果不加,很可能会被识别出为爬虫。

    Content-Type:

    也叫互联网媒体类型(Internet Media Type)或者MIME类型, 在HTTP协议消息头中,它用来表示具体请求中的媒体类型信息。 例如,text/html代表HTML格式,image/gif代表GIF图片, application/json代表JSON类型。

    更多对应关系可以查看此对照表:http://tool.oschina.net/commons。

  • 请求体

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

    设置Content-Type为application/x-www-form-urlencoded,以表单数据的形式提交 Content-Type设置为application/json来提交JSON数据,或者设置为multipart/form-data来上传文件 在爬虫中,

    如果要构造POST请求,需要使用正确的Content-Type,并了解各种请求库的各个参数设置时使用的是哪种Content-Type,不然可能会导致POST提交后无法正常响应。

9. HTTP响应报文

即从Web服务器到客户机(浏览器)的应答。报文的所有字段都是ASCII码。

  • 响应报文中的状态码

    状态码(Status-Code)是响应报文状态行中包含的一个3位数字,指明特定的请求是否被满足,如果没有满足,原因是什么。状态码分为以下五类:

    • 1xx:指示信息–表示请求已接收,继续处理。
    • 2xx:成功–表示请求已被成功接收、理解、接受。
    • 3xx:重定向–要完成请求必须进行更进一步的操作。
    • 4xx:客户端错误–请求有语法错误或请求无法实现。
    • 5xx:服务器端错误–服务器未能实现合法的请求。

    常见状态代码、状态描述的说明如下:

    200 OK:客户端请求成功。
    400 Bad Request:客户端请求有语法错误,不能被服务器所理解。
    401 Unauthorized:请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用。
    403 Forbidden:服务器收到请求,但是拒绝提供服务。
    404 Not Found:请求资源不存在,举个例子:输入了错误的URL。
    500 Internal Server Error:服务器发生不可预期的错误。
    503 Server Unavailable:服务器当前不能处理客户端的请求,一段时间后可能恢复正常,举个例子:HTTP/1.1 200 OK(CRLF)。
      
    
  • 响应头

    Date:

    标识响应产生的时间。

    Last-Modified:

    指定资源的最后修改时间。

    Content-Encoding:

    指定响应内容的编码。

    Server:

    包含服务器的信息,比如名称、版本号等。

    Content-Type:

    文档类型,指定返回的数据类型是什么,如text/html代表返回HTML文档,application/x-javascript则代表返回JavaScript文件,image/jpeg则代表返回图片。

    Set-Cookie:

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

    Expires:

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

  • 响应体

    响应的正文数据都在响应体中,比如请求网页时,它的响应体就是网页的HTML代码;请求一张图片时,它的响应体就是图片的二进制数据。我们做爬虫请求网页后,要解析的内容就是响应体

10. HTTP代理

HTTP代理又称Web缓存或代理服务器(Proxy Server),是一种网络实体, 能代表浏览器发出HTTP请求,并将最近的一些请求和响应暂存在本地磁盘中, 当请求的Web页面先前暂存过,则直接将暂存的页面发给客户端(浏览器),无须再次访问Internet。


四、虚拟环境

1. virtualenv

Virtualenv用于在一台机器上创建多个独立的python环境

可以让每一个python项目单独使用一个环境,而不会影响python系统环境,也不会影响其他环境

可以隔离项目之间三方包的依赖

  • 安装

    pip install virtualenv

  • 创建虚拟环境

    在命令行里执行 virtualenv v1

  • 激活虚拟环境

    进入到v1中的Scripts目录下

    执行 activate

    命令行前面会多一个(v1)说明已经在虚拟环境中

  • 退出虚拟环境

    deactivate

  • pip的使用技巧 freeze

    当我们在做项目迁移时肯定也要将依赖的三方模块在要被迁移的电脑上安装,一个一个安装很麻烦

    pip freeze > install.txt

    安装时

    pip install -r install.txt

2. venv

要创建虚拟环境,请确定要放置它的目录,并将 venv 模块作为脚本运行目录路径:

python -m venv v1

在Windows上,运行:

v1\Scripts\activate.bat

在Unix或MacOS上,运行:

source v1/bin/activate


五、urllib 网络模块

python内置了网络模块urllib,可以通过urllib模块发送网络请求

1. request的使用

# urllib 中有一个request方法
from urllib import request

# 定义请求地址
base_url = ''

# 发送get请求
response = request.urlopen(url=base_url)

  • urllib.request.urlretrieve(url, filename=None, reporthook=None, data=None)

    将URL表示的网络对象复制到本地文件。

    参数 描述
    url 外部或者本地url
    filename 指定了保存到本地的路径(如果未指定该参数,urllib会生成一个临时文件来保存数据)
    reporthook 是一个回调函数,当连接上服务器、以及相应的数据块传输完毕的时候会触发该回调。我们可以利用这个回调函数来显示当前的下载进度。
    data 指post到服务器的数据。该方法返回一个包含两个元素的元组(filename, headers),filename表示保存到本地的路径,header表示服务器的响应头。

2. 构造请求头Request

我们可以利用urlopen()方法可以实现最基本请求的发起,

但这几个简单的参数并不足以构建一个完整的请求,

如果请求中需要加入headers(请求头)等信息,

我们就可以利用更强大的Request类来构建一个请求。

# 有些网站会有些反爬手段验证浏览器, 所以我们要伪装我们的爬虫程序让他看起来更像是浏览器
from urllib import request

# 定义请求地址
base_url = ''
# 定义请求头
headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.84 Safari/537.36'

}
# 构造请求头
req = request.Request(url=base_url,headers=headers)

# 发送get请求
response = request.urlopen(req)

# 处理响应
html=response.read().decode('utf-8')

with open('./xici.html','w',encoding='utf-8') as f:
    f.write(html)

import urllib.request
import urllib.parse

url = 'http://httpbin.org/post'
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36'}
headers['Host'] = 'httpbin.org'
dict = {'name':'Germey'}

data = urllib.parse.urlencode(dict).encode('utf-8')
#data参数如果要传必须传bytes(字节流)类型的,如果是一个字典,先用urllib.parse.urlencode()编码。
request = urllib.request.Request(url = url,data = data,headers = headers,method = 'POST')

response = urllib.request.urlopen(request)
html = response.read().decode('utf-8')

print(html)

3. url地址中文转码

urllib 中除了提供了request 还提供了parse用于处理url请求地址 中文要进行转码

parse

  • parse.urlencode
from urllib import request,parse
# 定义请求地址
# http://www.baidu.com/s?wd=美女
# http://www.baidu.com/s?wd=%e7%be%8e%e5%a5%b3
base_url =  'http://www.baidu.com/s?'   

# 定义参数
data = {
    wd: '美女'
}

# 解析url
msg = parse.urlencode(data)
# 拼接url
url = base_url+msg

# 发送请求
response = request.urlopen(url=url)

  • urllib.parse.urljoin() 拼接url地址

split

replace

str.replace(old, new[, max])

strip()

Python strip() 方法用于移除字符串头尾指定的字符(默认为空格或换行符)或字符序列。

注意:该方法只能删除开头或是结尾的字符,不能删除中间部分的字符。

  • 格式 :

    str.strip([chars]);
      
    
  • 参数:

    chars – 移除字符串头尾指定的字符序列。

  • 返回值:

    返回移除字符串头尾指定的字符生成的新字符串。

    str = "00000003210Runoob01230000000"; 
    print str.strip( '0' );  # 去除首尾字符 0
       
       
    str2 = "   Runoob      ";   # 去除首尾空格
    print str2.strip();
      
    以上实例输出结果如下:
    3210Runoob0123
    Runoob
      
    

format()

format()基本语法是通过 {} 和 : 来代替以前的 % 。

  • format 函数可以接受不限个参数,位置可以不按顺序。

    >>>"{} {}".format("hello", "world")    # 不设置指定位置,按默认顺序
    'hello world'
       
    >>> "{0} {1}".format("hello", "world")  # 设置指定位置
    'hello world'
       
    >>> "{1} {0} {1}".format("hello", "world")  # 设置指定位置
    'world hello world'
      
    
  • 也可以设置参数:

    print("网站名:{name}, 地址 {url}".format(name="菜鸟教程", url="www.runoob.com"))
       
    # 通过字典设置参数
    site = {"name": "菜鸟教程", "url": "www.runoob.com"}
    print("网站名:{name}, 地址 {url}".format(**site))
       
    # 通过列表索引设置参数
    my_list = ['菜鸟教程', 'www.runoob.com']
    print("网站名:{0[0]}, 地址 {0[1]}".format(my_list))  # "0" 是必须的
      
    输出结果为:
    网站名:菜鸟教程, 地址 www.runoob.com
    网站名:菜鸟教程, 地址 www.runoob.com
    网站名:菜鸟教程, 地址 www.runoob.com
      
    
  • 也可以向 str.format() 传入对象

    数字格式化

4. ssl认证问题

https是基于ssl进行的加密认证,所以在发送https请求时需要认证证书,我们可以设置跳过认证

import ssl
ssl._create_default_https_context = ssl._create_unverified_context # 默认不需要校验网站证书

5. 代理的使用

代理的使用可以在一些方面提升数据采集的速度,和防止ip被封,也算是一种反反爬的手段

  • 非认证代理

    # 定义代理 
    proxy={
       'http': 'http://ip:prot',
       'https': 'https://ip:端口号'
    }
    # 创建代理对象
    proxy_handler = request.ProxyHandler(proxy)
    # 自定义请求方法
    opener = requests.build_opener(proxy_handler)
      
    # 发送请求
    response = opener.open(url)
      
    
  • 认证代理

    # 定义代理
    proxy = {
        'http':'http://user:password@ip:port',
        'https':'https://user:password@ip:port',
    }
    # 创建代理对象
    proxy_handler = request.ProxyHandler(proxy)
    # 自定义请求对象
    opener = request.build_opener(proxy_handler)
    # 发送请求
    response = opener.open(url)
      
    

六、requests模块

requests 是一个第三方的网络模块,对urllib进行了封装,简化了urllib的操作,使用起来更方便

  • 安装pip install requests

  • 发送get请求

    格式: requests.get(url,headers=headers)

  • 发送get请求

    格式: requests.get(url,headers=headers)

  • 发送post请求

    格式: requests.post(url,headers=headers,data=data)

    data={
      
       }
    requests.post(url,headers=headers,data=data)
      
    
  • 以encoding解析返回内容

    格式: r.text

  • 添加代理

    格式: requests.get(url,proxies=proxy)

    proxy={
          'http': 'http://ip:port',
          'https': 'https://ip:port'
      }
      proxy={
          'http': 'http://user:password@ip:port',
          'https': 'https://user:password@ip:port'
      }
      requests.get(url,proxies=proxy)
      
    
  • 构建会话信息

    格式: session = requests.session()

     # 实例化会话
     session = requests.session()
      
     # 发送请求
     session.get()
     session.post()
      
    

七、re模块–正则

又称规则表达式,通常被用来检索,替换符合某个规则的文本字符串

  • 常用方法:

    compile: 将正则表达式模式编译成一个正则表达式对象
    search:从字符串开始检索到结尾,返回第一次匹配到的内容
    match : 默认在规则前边加上^
    findall : 从头到尾检索,匹配所有符合规则的字符串,返回列表
    finditer :返回迭代器
      
    

1. 正则表达式修饰符 - 可选标志

正则表达式可以包含一些可选标志修饰符来控制匹配的模式。修饰符被指定为一个可选的标志。多个标志可以通过按位 OR( ) 它们来指定。如 re.I re.M 被设置成 I 和 M 标志:
修饰符 描述
re.I 使匹配对大小写不敏感, 忽略大小写
re.L 做本地化识别(locale-aware)匹配
re.M 多行匹配,影响 ^ 和 $
re.S 使 . 匹配包括换行在内的所有字符
re.U 根据Unicode字符集解析字符。这个标志影响 \w, \W, \b, \B.
re.X 该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解。

2. 正则表达式模式

模式 描述
^ 匹配字符串的开头
$ 匹配字符串的末尾。
. 匹配任意字符,除了换行符,当re.DOTALL标记被指定时,则可以匹配包括换行符的任意字符。
[…] 用来表示一组字符,单独列出:[amk] 匹配 ‘a’,’m’或’k’
[^…] 不在[]中的字符:[^abc] 匹配除了a,b,c之外的字符。
re* 匹配0个或多个的表达式。
re+ 匹配1个或多个的表达式。
re? 匹配0个或1个由前面的正则表达式定义的片段,非贪婪方式
re{ n} 匹配n个前面表达式。例如,”o{2}”不能匹配”Bob”中的”o”,但是能匹配”food”中的两个o。
re{ n,} 精确匹配n个前面表达式。例如,”o{2,}”不能匹配”Bob”中的”o”,但能匹配”foooood”中的所有o。”o{1,}”等价于”o+”。”o{0,}”则等价于”o*“。
re{ n, m} 匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式
a| b 匹配a或b
(re) 匹配括号内的表达式,也表示一个组
(?imx) 正则表达式包含三种可选标志:i, m, 或 x 。只影响括号中的区域。
(?-imx) 正则表达式关闭 i, m, 或 x 可选标志。只影响括号中的区域。
(?: re) 类似 (…), 但是不表示一个组
(?imx: re) 在括号中使用i, m, 或 x 可选标志
(?-imx: re) 在括号中不使用i, m, 或 x 可选标志
(?#…) 注释.
(?= re) 前向肯定界定符。如果所含正则表达式,以 … 表示,在当前位置成功匹配时成功,否则失败。但一旦所含表达式已经尝试,匹配引擎根本没有提高;模式的剩余部分还要尝试界定符的右边。
(?! re) 前向否定界定符。与肯定界定符相反;当所含表达式不能在字符串当前位置匹配时成功。
(?> re) 匹配的独立模式,省去回溯。
\w 匹配数字字母下划线
\W 匹配非数字字母下划线
\s 匹配任意空白字符,等价于 [\t\n\r\f]。
\S 匹配任意非空字符
\d 匹配任意数字,等价于 [0-9]。
\D 匹配任意非数字
\A 匹配字符串开始
\Z 匹配字符串结束,如果是存在换行,只匹配到换行前的结束字符串。
\z 匹配字符串结束
\G 匹配最后匹配完成的位置。
\b 匹配一个单词边界,也就是指单词和空格间的位置。例如, ‘er\b’ 可以匹配”never” 中的 ‘er’,但不能匹配 “verb” 中的 ‘er’。
\B 匹配非单词边界。’er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。
\n, \t, 等。 匹配一个换行符。匹配一个制表符, 等
\1…\9 匹配第n个分组的内容。
\10 匹配第n个分组的内容,如果它经匹配。否则指的是八进制字符码的表达式。

3. re函数、方法使用

若匹配规则里有1个括号——返回的是括号所匹配到的结果,

若匹配规则里有多个括号——返回多个括号分别匹配到的结果,

若匹配规则里没有括号——就返回整条语句所匹配到的结果。

  • compile 函数

    compile 函数用于编译正则表达式,生成一个正则表达式( Pattern )对象,供 match() 和 search() 这两个函数使用。

    格式:re.compile(pattern[, flags])

    参数 描述
    pattern 一个字符串形式的正则表达式
    flags 可选 表示匹配模式,比如忽略大小写,多行模式等
    >>>import re
    >>> pattern = re.compile(r'\d+')                    # 用于匹配至少一个数字
    >>> m = pattern.match('one12twothree34four')        # 查找头部,没有匹配
    >>> print m
    None
    >>> m = pattern.match('one12twothree34four', 2, 10) # 从'e'的位置开始匹配,没有匹配
    >>> print m
    None
    >>> m = pattern.match('one12twothree34four', 3, 10) # 从'1'的位置开始匹配,正好匹配
    >>> print m                                         # 返回一个 Match 对象
    <_sre.SRE_Match object at 0x10a42aac0>
    >>> m.group(0)   # 可省略 0
    '12'
    >>> m.start(0)   # 可省略 0
    3
    >>> m.end(0)     # 可省略 0
    5
    >>> m.span(0)    # 可省略 0
    (3, 5)
      
    

    在上面,当匹配成功时返回一个 Match 对象,其中:

    • group([group1, …]) 方法用于获得一个或多个分组匹配的字符串,当要获得整个匹配的子串时,可直接使用 group()group(0)
    • start([group]) 方法用于获取分组匹配的子串在整个字符串中的起始位置(子串第一个字符的索引),参数默认值为 0;
    • end([group]) 方法用于获取分组匹配的子串在整个字符串中的结束位置(子串最后一个字符的索引+1),参数默认值为 0;
    • span([group]) 方法返回 (start(group), end(group))
  • re.search方法

    re.search 扫描整个字符串并返回第一个成功的匹配。

    格式: re.search(pattern, string, flags=0)

    参数 描述
    pattern 匹配的正则表达式
    string 要匹配的字符串。
    flags 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。参见:正则表达式修饰符 - 可选标志
  • re.match函数

    尝试从字符串的起始位置匹配一个模式,如果不是起始位置匹配成功的话,match()就返回none。

    格式:re.match(pattern, string, flags=0)

    参数 描述
    pattern 匹配的正则表达式
    string 要匹配的字符串。
    flags 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。参见:正则表达式修饰符 - 可选标志

re.match与re.search的区别

re.match只匹配字符串的开始,如果字符串开始不符合正则表达式,则匹配失败,函数返回None;

re.search匹配整个字符串,直到找到一个匹配。

  • group(num)

    匹配的整个表达式的字符串,group() 可以一次输入多个组号,在这种情况下它将返回一个包含那些组所对应值的元组。

    import re
    line = "Cats are smarter than dogs";
       
    searchObj = re.search( r'(.*) are (.*?) .*', line, re.M|re.I)
       
    if searchObj:
       print ("searchObj.group() : ", searchObj.group())
       print ("searchObj.group(1) : ", searchObj.group(1))
       print ("searchObj.group(2) : ", searchObj.group(2))
    else:
       print ("Nothing found!!")
         
    以上实例执行结果如下:   
    searchObj.group() :  Cats are smarter than dogs
    searchObj.group(1) :  Cats
    searchObj.group(2) :  smarter
      
    
  • findall

    在字符串中找到正则表达式所匹配的所有子串,并返回一个列表,如果没有找到匹配的,则返回空列表。

    注意: match 和 search 是匹配一次 findall 匹配所有。

    格式: findall(string[, pos[, endpos]])

    参数 描述
    string 待匹配的字符串。
    pos 可选参数,指定字符串的起始位置,默认为 0。
    endpos 可选参数,指定字符串的结束位置,默认为字符串的长度。
    ['123', '456']import re
       
    pattern = re.compile(r'\d+')   # 查找数字
    result1 = pattern.findall('runoob 123 google 456')
    result2 = pattern.findall('run88oob123google456', 0, 10)
       
    print(result1)
    print(result2)
      
    输出结果:
    ['123', '456']
    ['88', '12']
      
    
  • re.finditer

    和 findall 类似,在字符串中找到正则表达式所匹配的所有子串,并把它们作为一个迭代器返回。

    格式: finditer(pattern, string, flags=0)

    参数 描述
    pattern 匹配的正则表达式
    string 要匹配的字符串。
    flags 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。参见:正则表达式修饰符 - 可选标志
    import re
       
    it = re.finditer(r"\d+","12a32bc43jf3") 
    for match in it: 
        print (match.group() )
          
    输出结果:
    12 
    32 
    43 
    3
      
    
  • re.split

    split 方法按照能够匹配的子串将字符串分割后返回列表

    格式: re.split(pattern, string[, maxsplit=0, flags=0])

    参数 描述
    pattern 匹配的正则表达式
    string 要匹配的字符串。
    maxsplit 分隔次数,maxsplit=1 分隔一次,默认为 0,不限制次数。
    flags 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。参见:正则表达式修饰符 - 可选标志

八、json模块–数据处理

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,它使得人们很容易的进行阅读和编写。同时也方便了机器进行解析和生成。适用于进行数据交互的场景,比如网站前台与后台之间的数据交互。

json简单说就是javascript中的对象和数组,所以这两种结构就是对象和数组两种结构,通过这两种结构可以表示各种复杂的结构

  • json.dumps()

    json.dumps() 用于将 Python 对象编码成 JSON 字符串。

  • json.loads()

    json.loads() 用于解码 JSON 数据。该函数返回 Python 字段的数据类型。

import requests
import json
response = requests.get('http://httpbin.org/get')
<br>res1 = json.loads(response.text) #太麻烦
res2=response.json() #直接获取json数据
print(res1 == res2) #True


九、jsonpath

1. 作用

类似于XPath在xml文档中的定位,JsonPath表达式通常是用来路径检索或设置Json的。

2. 操作符

符号 描述
$ 查询的根节点对象,用于表示一个json数据,可以是数组或对象
@ 过滤器断言(filter predicate)处理的当前节点对象,类似于java中的this字段
* 通配符,可以表示一个名字或数字
.. 可以理解为递归搜索,Deep scan. Available anywhere a name is required.
. 表示一个子节点
[‘’ (, ‘’)] 表示一个或多个子节点
[ (, )] 表示一个或多个数组下标
[start:end] 数组片段,区间为[start,end),不包含end
[?()] 过滤器表达式,表达式结果必须是boolean

3. 函数

可以在JsonPath表达式执行后进行调用,其输入值为表达式的结果。

名称 描述 输出
min() 获取数值类型数组的最小值 Double
max() 获取数值类型数组的最大值 Double
avg() 获取数值类型数组的平均值 Double
stddev() 获取数值类型数组的标准差 Double
length() 获取数值类型数组的长度 Integer

4. 过滤器

过滤器是用于过滤数组的逻辑表达式,一个通常的表达式形如:[?(@.age > 18)],可以通过逻辑表达式&&或   组合多个过滤器表达式,例如[?(@.price < 10 && @.category == ‘fiction’)],字符串必须用单引号或双引号包围,例如[?(@.color == ‘blue’)] or [?(@.color == “blue”)]。
操作符 描述
== 等于符号,但数字1不等于字符1(note that 1 is not equal to ‘1’)
!= 不等于符号
< 小于符号
<= 小于等于符号
> 大于符号
>= 大于等于符号
=~ 判断是否符合正则表达式,例如[?(@.name =~ /foo.*?/i)]
in 所属符号,例如[?(@.size in [‘S’, ‘M’])]
nin 排除符号
size size of left (array or string) should match right
empty 判空符号

5. 例子

让我们通过更多示例来练习JSONPath表达式。我们从一个表示书店(原始XML文件)的XML示例之后构建的简单JSON结构开始。

{ “store”:{
     “book”:[
      { “category”:“reference”,
         “author”:“Nigel Rees”,
         “title”:“世纪的谚语”,
         “价格”:8.95
      },
      { “类别”:“小说”,
         “作者”:“伊夫林沃”,
         “标题”:“荣誉之剑”,
         “价格”:12.99
      },
      { “类别”:“小说”,
         “作者”:“Herman Melville”,
         “title”:“Moby Dick”,
         “isbn”:“0-553-21311-3”,
         “price”:8.99
      },
      { “类别”:“小说”,
         “作者”:“JRR托尔金”,
         “标题”:“指环王”,
         “isbn”:“0-395-19395-8”,
         “价格”:22.99
      }
    ]
    “自行车”:{
       “颜色”:“红色”,
       “价格”:19.95
    }
  }
}

XPath的 JSONPath 结果
/store/book/author $.store.book[*].author 商店里所有书籍的作者
//author $..author 所有作者
/store/* $.store.* 商店里的所有东西,都是一些书和一辆红色的自行车。
/store//price $.store..price 商店里一切的价格。
//book[3 $..book[2] 第三本书
//book[last()] $..book[(@.length-1)] $..book[-1:] 最后一本书。
//book[position()<3] $..book[0,1] $..book[:2] 前两本书
//book[isbn] $..book[?(@.isbn)] 使用isbn number过滤所有书籍
//book[price<10] $..book[?(@.price<10)] 过滤所有便宜10以上的书籍
//* $..* XML文档中的所有元素。JSON结构的所有成员。

十、xpath

1. 简介

XPath 是一门在 XML 文档中查找信息的语言。XPath 可用来在 XML 文档中对元素和属性进行遍历。XPath 是 W3C XSLT 标准的主要元素,并且 XQuery 和 XPointer 都构建于 XPath 表达之上。

2. 使用

  • 安装

    pip install lxml
      
    
  • 导入

    from lxml import etree
      
    
  • 基本使用

    from lxml import etree
       
    wb_data = """
            <div>
                <ul>
                     <li class="item-0"><a href="link1.html">first item</a></li>
                     <li class="item-1"><a href="link2.html">second item</a></li>
                     <li class="item-inactive"><a href="link3.html">third item</a></li>
                     <li class="item-1"><a href="link4.html">fourth item</a></li>
                     <li class="item-0"><a href="link5.html">fifth item</a>
                 </ul>
             </div>
            """
    html = etree.HTML(wb_data)
    print(html)
    result = etree.tostring(html)
    print(result.decode("utf-8"))
      
    

3. Xpath语法

<?xml version="1.0" encoding="ISO-8859-1"?>

<bookstore>

<book>
  <title lang="eng">Harry Potter</title>
  <price>29.99</price>
</book>

<book>
  <title lang="eng">Learning XML</title>
  <price>39.95</price>
</book>

</bookstore>

3.1 选取节点

表达式 描述
nodename 选取此节点的所有子节点。
/ 从根节点选取。
// 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。
. 选取当前节点。
.. 选取当前节点的父节点。
@ 选取属性。

实例

在下面的表格中,我们已列出了一些路径表达式以及表达式的结果:

路径表达式 结果
bookstore 选取 bookstore 元素的所有子节点。
/bookstore 选取根元素 bookstore。注释:假如路径起始于正斜杠( / ),则此路径始终代表到某元素的绝对路径!
bookstore/book 选取属于 bookstore 的子元素的所有 book 元素。
//book 选取所有 book 子元素,而不管它们在文档中的位置。
bookstore//book 选择属于 bookstore 元素的后代的所有 book 元素,而不管它们位于 bookstore 之下的什么位置。
//@lang 选取名为 lang 的所有属性。

3.2 谓语(Predicates)

实例

在下面的表格中,我们列出了带有谓语的一些路径表达式,以及表达式的结果:

路径表达式 结果
/bookstore/book[1] 选取属于 bookstore 子元素的第一个 book 元素。
/bookstore/book[last()] 选取属于 bookstore 子元素的最后一个 book 元素。
/bookstore/book[last()-1] 选取属于 bookstore 子元素的倒数第二个 book 元素。
/bookstore/book[position()<3] 选取最前面的两个属于 bookstore 元素的子元素的 book 元素。
//title[@lang] 选取所有拥有名为 lang 的属性的 title 元素。
//title[@lang=’eng’] 选取所有 title 元素,且这些元素拥有值为 eng 的 lang 属性。
/bookstore/book[price>35.00] 选取 bookstore 元素的所有 book 元素,且其中的 price 元素的值须大于 35.00。
/bookstore/book[price>35.00]/title 选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00。

3.3 选取未知节点

XPath 通配符可用来选取未知的 XML 元素。

通配符 描述
* 匹配任何元素节点。
@* 匹配任何属性节点。
node() 匹配任何类型的节点。

实例

在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:

路径表达式 结果
/bookstore/* 选取 bookstore 元素的所有子元素。
//* 选取文档中的所有元素。
//title[@*] 选取所有带有属性的 title 元素。

3.4 选取若干路径

通过在路径表达式中使用“ ”运算符,您可以选取若干个路径。

实例

在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:

路径表达式 结果
//book/title | //book/price 选取 book 元素的所有 title 和 price 元素。
//title | //price 选取文档中的所有 title 和 price 元素。
/bookstore/book/title | //price 选取属于 bookstore 元素的 book 元素的所有 title 元素,以及文档中所有的 price 元素。

十一、进程线程协程

1. 概念

1.1 线程

线程是一个基本的 CPU 执行单元。它必须依托于进程存活。一个线程是一个 CPU 执行时所需要的一串指令。

1.2 进程

进程是指一个程序在给定数据集合上的一次执行过程,是系统进行资源分配和运行调用的独立单位。可以简单地理解为操作系统中正在执行的程序。也就说,每个应用程序都有一个自己的进程。

每一个进程启动时都会最先产生一个线程,即主线程。然后主线程会再创建其他的子线程。

1.3 两者的区别

  • 线程必须在某个进程中执行。
  • 一个进程可包含多个线程,其中有且只有一个主线程。
  • 多线程共享同个地址空间、打开的文件以及其他资源。
  • 多进程共享物理内存、磁盘、打印机以及其他资源。

2. Python 多进程

2.1 创建多进程Process

Python 要进行多进程操作,需要用到muiltprocessing库中的Process

  • 方法:使用Process

    from multiprocessing import Process  
      
    def show(name):
        print("Process name is " + name)
      
    if __name__ == "__main__": 
        proc = Process(target=show, args=('subprocess',))  
        proc.start()  
        proc.join()
      
    

2.2 多进程通信Queue

进程之间不共享数据的。如果进程之间需要进行通信,则要用到Queue模块或者Pipi模块来实现

  • Queue

    Queue 是多进程安全的队列,可以实现多进程之间的数据传递。它主要有两个函数,putget

    put() 用以插入数据到队列中,put 还有两个可选参数:blocked 和 timeout。如果 blocked 为 True(默认值),并且 timeout 为正值,该方法会阻塞 timeout 指定的时间,直到该队列有剩余的空间。如果超时,会抛出 Queue.Full 异常。如果 blocked 为 False,但该 Queue 已满,会立即抛出 Queue.Full 异常。

    get()可以从队列读取并且删除一个元素。同样,get 有两个可选参数:blocked 和 timeout。如果 blocked 为 True(默认值),并且 timeout 为正值,那么在等待时间内没有取到任何元素,会抛出 Queue.Empty 异常。如果blocked 为 False,有两种情况存在,如果 Queue 有一个值可用,则立即返回该值,否则,如果队列为空,则立即抛出 Queue.Empty 异常。

    from multiprocessing import Process, Queue
       
    def put(queue):
        queue.put('Queue 用法')
       
    if __name__ == '__main__':
        queue = Queue()
        pro = Process(target=put, args=(queue,))
        pro.start()
        print(queue.get())   
        pro.join()
      
    

2.3 进程池Pool

创建多个进程,我们可以使用Pool模块来搞定。

Pool 常用的方法如下:

方法 含义
apply() 同步执行(串行)
apply_async() 异步执行(并行)
terminate() 立刻关闭进程池
join() 主进程等待所有子进程执行完毕。必须在close或terminate()之后使用
close() 等待所有进程结束后,才关闭进程池
from multiprocessing import Pool
def show(num):
    print('num : ' + str(num))

if __name__=="__main__":
    pool = Pool(processes = 3)
    for i in range(6):
        # 维持执行的进程总数为processes,当一个进程执行完毕后会添加新的进程进去
        pool.apply_async(show, args=(i, ))       
    print('======  apply_async  ======')
    pool.close()
    #调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束
    pool.join()

3 Python 多线程

3.1 创建多线程threading

Python提供两个模块进行多线程的操作,分别是threadthreading

  • 方法1:直接使用threading.Thread()

    import threading
      
    # 这个函数名可随便定义
    def run(n):
        print("current task:", n)
      
    if __name__ == "__main__":
        t1 = threading.Thread(target=run, args=("thread 1",))
        t2 = threading.Thread(target=run, args=("thread 2",))
        t1.start()
        t2.start()
      
    

3.2 线程合并

Join函数执行顺序是逐个执行每个线程,执行完毕后继续往下执行。主线程结束后,子线程还在运行,join函数使得主线程等到子线程结束时才退出。

import threading

def count(n):
    while n > 0:
        n -= 1

if __name__ == "__main__":
    t1 = threading.Thread(target=count, args=(100000,))
    t2 = threading.Thread(target=count, args=(100000,))
    t1.start()
    t2.start()
    # 将 t1 和 t2 加入到主线程中
    t1.join()
    t2.join()

4. 协程

子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕。所以子程序调用是通过栈实现的,一个线程就是执行一个子程序。子程序调用总是一个入口,一次返回,调用顺序是明确的。而协程的调用和子程序不同。

协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。

4.1 主要的优点

  • 执行效率极高,因为子程序切换(函数)不是线程切换,由程序自身控制,没有切换线程的开销。所以与多线程相比,线程的数量越多,协程性能的优势越明显。
  • 不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在控制共享资源时也不需要加锁,因此执行效率高很多。

4.2 gevent使用

# 导入gevent包
import gevent
import time
# 猴子 monkey, 猴子补丁
from gevent import monkey
monkey.patch_all()
import requests

def visit_web_page(url):
    print('正在下载url: ', url)
    response = requests.get(url)
    print('url : code=' + str(response.status_code) + 'URL:' + url)

if __name__ == '__main__':

    # 生成一个类
    g1 = gevent.spawn(visit_web_page, 'http://www.baidu.com')
    g2 = gevent.spawn(visit_web_page, 'https://www.python.org')
    g3 = gevent.spawn(visit_web_page, 'http://www.sina.com')
    # 等待这些协程结束
    gevent.joinall([g1, g2, g3])

5. 进程、线程和协程的区别

  • 进程:

    进程之间不共享任何状态,进程的调度由操作系统完成,每个进程都有自己独立的内存空间,进程间通讯主要是通过信号传递的方式来实现的,实现方式有多种,信号量、管道、事件等,任何一种方式的通讯效率都需要过内核,导致通讯效率比较低。由于是独立的内存空间,上下文切换的时候需要保存先调用栈的信息、cpu各寄存器的信息、虚拟内存、以及打开的相关句柄等信息,所以导致上下文进程间切换开销很大,通讯麻烦。

  • 线程:

    线程之间共享变量,解决了通讯麻烦的问题,但是对于变量的访问需要锁,线程的调度主要也是有操作系统完成,一个进程可以拥有多个线程,但是其中每个线程会共享父进程像操作系统申请资源,这个包括虚拟内存、文件等,由于是共享资源,所以创建线程所需要的系统资源占用比进程小很多,相应的可创建的线程数量也变得相对多很多。线程时间的通讯除了可以使用进程之间通讯的方式以外还可以通过共享内存的方式进行通信,所以这个速度比通过内核要快很多。另外在调度方面也是由于内存是共享的,所以上下文切换的时候需要保存的东西就像对少一些,这样一来上下文的切换也变得高效。

  • 协程:

    协程的调度完全由用户控制,一个线程可以有多个协程,用户创建了几个线程,然后每个线程都是循环按照指定的任务清单顺序完成不同的任务,当任务被堵塞的时候执行下一个任务,当恢复的时候再回来执行这个任务,任务之间的切换只需要保存每个任务的上下文内容,就像直接操作栈一样的,这样就完全没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快;另外协程还需要保证是非堵塞的且没有相互依赖,协程基本上不能同步通讯,多采用一步的消息通讯,效率比较高。


十二、Scrapy 框架

1. Scrapy五大基本构成:

Scrapy框架主要由五大组件组成,它们分别是调度器(Scheduler)、下载器(Downloader)、爬虫(Spider)和实体管道(Item Pipeline)、Scrapy引擎(Scrapy Engine)。下面我们分别介绍各个组件的作用。

(1)、调度器(Scheduler):

调度器,说白了把它假设成为一个URL(抓取网页的网址或者说是链接)的优先队列,由它来决定下一个要抓取的网址是 什么,同时去除重复的网址(不做无用功)。用户可以自己的需求定制调度器。

(2)、下载器(Downloader):

下载器,是所有组件中负担最大的,它用于高速地下载网络上的资源。Scrapy的下载器代码不会太复杂,但效率高,主 要的原因是Scrapy下载器是建立在twisted这个高效的异步模型上的(其实整个框架都在建立在这个模型上的)。

(3)、 爬虫(Spider):

爬虫,是用户最关心的部份。用户定制自己的爬虫,用于从特定的网页中提取自己需要的信息,即所谓的实体(Item)。 用户也可以从中提取出链接,让Scrapy继续抓取下一个页面。

(4)、 实体管道(Item Pipeline):

实体管道,用于处理爬虫提取的实体。主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。

(5)、Scrapy引擎(Scrapy Engine):

Scrapy引擎是整个框架的核心.它用来控制调试器、下载器、爬虫。实际上,引擎相当于计算机的CPU,它控制着整个流程。

组件 作用 实现
Scrapy Engine(引擎) 总指挥: 负责数据和信号的在不同模块间的传递 Scrapy已经实现
Scheduler(调度器) 一个队列, 存放引擎发过来的request请求 Scrapy已经实现
Downloaer(下载器) 下载器把引擎发过来的requests请求,并返回给引擎 Scrapy已经实现
Spider 处理引擎发来的response, 提取数据, 提取url, 并交给引擎 需要手写
Iteam Pipeline(管道) 处理引擎传过来的数据, 比如储存 需要手写
Downloader Middlewares(下载中间件) 可以自定义的下载扩展, 比如设置代理 一般不用手写
Spider Middlewares(爬虫中间件) 可以自定义requests请求和进行responseguolkv 一般不用手写

2. scrapy 框架的工作流程

Scrapy-流程图

scrapy框架的工作流程:

  1. 首先Spiders(爬虫)将需要发送请求的url(requests)经ScrapyEngine(引擎)交给Scheduler(调度器)。
  2. Scheduler(排序,入队)处理后,经ScrapyEngine,DownloaderMiddlewares(可选,主要有User_Agent, Proxy代理)交给Downloader。
  3. Downloader向互联网发送请求,并接收下载响应(response)。将响应(response)经ScrapyEngine,SpiderMiddlewares(可选)交给Spiders。
  4. Spiders处理response,提取数据并将数据经ScrapyEngine交给ItemPipeline保存(可以是本地,可以是数据库)。
  5. 提取url重新经ScrapyEngine交给Scheduler进行下一个循环。直到无Url请求程序停止结束。

3. 使用介绍

​ (1) 开始项目 scrapy startproject 项目名

​ (2) 生成爬虫程序 scrapy genspider 爬虫名字 域名

​ (3) 执行爬虫程序 Scrapy crawl 爬虫名

​ (4) shell调试工具 scrapy shell

4. 编写Scrapy 爬虫步骤

  • 新建项目 (scrapy startproject xxx):新建一个新的爬虫项目
  • 明确目标 编写 (items.py):明确你想要抓取的目标
  • 制作爬虫 (spiders/xxspider.py):制作爬虫开始爬取网页
  • 存储内容 (pipelines.py):设计管道存储爬取内容
  • 设置配置 (settings.py) :根据需求修改配置

(1)、run.py

from scrapy.cmdline import execute
execute('scrapy crawl 爬虫程序名 '.split())

(2)、items.py

​ Item是保存爬取到数据的容器,与字典类似 ,提供了额外的保护机制来避免拼写错误,导致的未定义字段错误。

​ 在使用item的时候,以字典的方式访问。

items.py
class __(自定义)Item(scrapy,Item):
	__(自定义) = scrapy.field()

(3)、spiders\ (爬虫程序名).py

  • 将request 传入 scheduler

    req = scrapy.Request(url,callback = 函数)
    yield req
    req.meta[] = response.meta[]
    yield req
      
    
  • 使用一个函数替换 start url

    def start_requests(self):
    	base_url = '...'
        req = scrapy.Request(url=url, callback=self.parse)
    	yield req
      
    
  • post请求

    req = scrapy.FormRequest(url,callback = 函数,formdata = data)
    yield req
      
    
  • headers 添加

    # 1.在settings 中添加: 全局使用整个项目
    DEFAULT_REQUEST_HEADERS = {
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
        'Accept-Language': 'en',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36',
    }
      
    # 2.在爬虫程序中添加: 作用于整个程序
    custom_settings = {
            'DEFAULT_REQUEST_HEADERS' : {
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
            'Accept-Language': 'en',
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36',
            }
        }
          
    # 3.在 request 中添加: 作用范围是 request
    req = scrapy.Request(url=url, callback=self.parse)
    req.headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36'
    yield req
      
    
  • 将 item 传递到 item.py

    # spiders\ (爬虫程序名).py
    item = QidianItem()
    # item 的使用, 必须是字典的形式
    item['chapter'] = response.meta['chapter']
    item['content'] = content
    yield item
      
    # item.py
    class QidianItem(scrapy.Item):
        chapter = scrapy.Field()
        content = scrapy.Field()
      
    
  • 用于在response 之间传递信息的一个字典

    response.meta
      
    
  • 数据解析

    response.re()
    # 根据传入的正则表达式对数据进行提取,返回 Unicode 字符串 list 列表
      
    response.xpath()/css()
    # 返回该表达式所对应的所有节点的 selector list 列表
      
    response.xpath().extract()
    # 序列化该节点为 Unicode 字符串并返回 list
    # extract()方法会把原数据的 selector 类型转变为列表类型
      
    xpath().extract()[0] = xpath().extract_fist()
    # 得到第一个值,类型为字符串。
      
    
  • 拼接url

    response.urljoin(next_page)
      
    
  • 获取这个response对应的 url

    response.url
      
    
  • 图片下载

    # item.py
    class DoubanItem(scrapy.Item):
        image_urls = scrapy.Field()
      
    # spiders\ (爬虫程序名).py
    from itxdl.items import DoubanItem
      
    1. 将image_url 存入 item 中的 image_urls
    i = DoubanItem()
    i['image_urls'] = []
    i['image_urls'].append(url)
      
    2.再将item 放入 pipeline
    yield i
      
    3.在 settings 添加配置
    ITEM_PIPELINES = {
       'scrapy.pipelines.images.ImagesPipeline': 1,
    }
      
    IMAGES_URLS_FIELD = 'image_urls'	# 下载的内容,与 item 中对应一致
    IMAGES_STORE='download'		# 存放的位置
      
      
    

(4)、pipelines.py

class __Pipeline(object):
	def process_item(self,item,spider):
	# 此 item 就是 spider 中 yield item 的那个
		return item 
		

注意:pipeline 写好之后,必须在配置文件进行配置 ITEM_PIPELINES = {…}

数据库存储(通用):

# db.py 连接数据库、执行sql语句
import pymysql

class DB():
    def __init__(self,database = 'py05',user='root',password='123456',port = 3306,host = 'localhost'):
        self.db = pymysql.connect(host = host,port=port,user = user,database = database,password = password,charset ='utf8mb4')
        self.cursor = self.db.cursor()
    
    def update(self,sql, data):
        try:
            self.cursor.execute(sql, data)
            self.db.commit()
        except:
            print("操作失败,请检查sql语句")
    def query(self,sql):
        try:
            self.cursor.execute(sql)
            data = self.cursor.fetchall()
            return data
        except:
            print("查询失败,请检查sql语句")
    def __del__(self):
        self.cursor.close()
        self.db.close()

# pipelines.py

from itxdl.db import DB	# 导入编写好的db.py
class MySQLPipeline(object):
    def __init__(self):
        self.helper = DB()

    def process_item(self, item, spider):
        if hasattr(item, 'get_insert_sql_and_data'):
            insert_sql, data = item.get_insert_sql_and_data()
            self.helper.update(insert_sql, data)
        return item

# items.py
# class __Item(scrapy.Item):
class DoubanNoteItem(scrapy.Item):
    title = scrapy.Field()
    content = scrapy.Field()

    def get_insert_sql_and_data(self):
        insert_sql = 'INSERT INTO douban_test (title, content) VALUES(%s, %s)'
        data = (self['title'], self['content'])

        return insert_sql, data

# settings.py
ITEM_PIPELINES = {
   'itxdl.pipelines.MySQLPipeline': 300,
}

(5)、settings.py

  • 爬虫协议,限制爬虫程序爬取内容的范围

    ROBOTSTXT_OBEY = False
      
    
  • 并发请求数量

    CONCURRENT_REQUESTS = 32
      
    
  • 下载器间隔,下载器从同一网站下载连续页面的等待时间,限制爬取速度

    DOWMLOAD_DELAY = 3
      
    
  • 启用一个Item Pipeline组件,整型值确定了运行顺序,通常在0~1000 内

    ITEM_PIPELINES = {
       'scrapy.pipelines.images.ImagesPipeline': 1,
       '__(项目名).pipelines.MySQLPipeline(与pipelines中类名对应)': 300,
    }
      
    
  • 如果在item中有 image_urls 那么 就会将图片下载到 download 文件夹下(自行添加)

    IMAGES_URLS_FIELD = 'image_urls'
    IMAGES_STORE='download'
      
    

十三、selenium

  • 基本用法

    # Selenium是一个自动化测试网页的工具
      
    from selenium import webdriver
      
    # 打开chrome浏览器
    driver = webdriver.Chrome()
      
    # 访问网址
    driver.get('http://www.baidu.com')
      
    # 在输入框中输入文字
    driver.find_element_by_id('kw').send_keys('大长腿是什么感觉')
    driver.find_element_by_id('su').click()
    import time
    time.sleep(10)
    driver.quit()
      
    
    from selenium import webdriver
    import time
      
    driver = webdriver.Chrome(executable_path='D:\\chromedriver.exe')
      
    driver.get('https://www.weibo.com/')
    time.sleep(30)
      
    driver.find_element_by_id('loginname').send_keys('18510556963')
    time.sleep(5)
    driver.find_element_by_name('password').send_keys('yaoqinglin2011')
    time.sleep(1)
    driver.find_element_by_class_name('W_btn_a').click()
    time.sleep(10)
      
    cookie_list = driver.get_cookies()
    print(cookie_list)
      
    url = 'https://account.weibo.com/set/index?topnav=1&wvr=6'
    cookie_dict={}
      
    for cookie in  cookie_list:
        cookie_dict[cookie['name']] = cookie['value']
      
    print(cookie_dict)
      
    response = requests.get(url, cookies=cookie_dict)
    with open('weibo.html', 'wb') as f:
        f.write(response.content)
      
    
  • 获取网页源代码

    from selenium import webdriver
      
    # 打开chrome浏览器
    driver = webdriver.Chrome()
      
    # 访问网址
    driver.get('http://www.baidu.com')
      
    # 输出网页源代码
    print(driver.page_source)
      
    
  • cookie操作

    get_cookies()
    delete_all_cookes()
    add_cookie()
      
    
  • 浏览器的前进和后退

    back()
    forward()
      
    
  • 关闭浏览器

    driver.quit()
    driver.close()
    close方法是关闭当前窗口。(当前窗口的意思就是表示driver现在正在操作的窗口)如果当前窗口只有一个tab,那么这个close方法就相当于关闭了浏览器。quit方法就直接关闭是直接退出并关闭浏览器所有打开的tab窗口。所以,close方法一般关闭一个tab,quit方法才是我们认为的完全关闭浏览器方法。
      
    

打赏一个呗

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦