Python spiders基础学习笔记

爬虫简介

什么是爬虫:

通过编写程序,模拟浏览器上网,然后让其去互联网上抓取数据的过程。

爬虫的价值:

实际应用
就业

爬虫究竟是合法还是违法的?

  • 在法律中是不被禁止
  • 具有违法风险
  • 善意爬虫 恶意爬虫

爬虫带来的风险可以体现在如下2方面:

  • 爬虫干扰了被访问网站的正常运营
  • 爬虫抓取了收到法律保护的特定类型的数据或信息

如何在使用编写爬虫的过程中避免进入局子的厄运呢?

  • 时常的优化自己的程序,避免干扰被访问网站的正常运行
  • 在使用,传播爬取到的数据时,审查抓取到的内容,如果发现了涉及到用户隐私,商业机密等敏感内容需要及时停止爬取或传播

爬虫在使用场景中的分类

  • 通用爬虫:抓取系统重要组成部分。抓取的是一整张页面数据。
  • 聚焦爬虫:是建立在通用爬虫的基础之上。抓取的是页面中特定的局部内容。
  • 增量式爬虫:检测网站中数据更新的情况。只会抓取网站中最新更新出来的数据。

反爬机制

  • 门户网站,可以通过制定相应的策略或者技术手段,防止爬虫程序进行网站数据的爬取。

反反爬策略

  • 爬虫程序可以通过制定相关的策略或者技术手段,破解门户网站中具备的反爬机制,从而可以获取门户网站中相关的数据。

robots.txt协议:

  • 君子协议。规定了网站中哪些数据可以被爬虫爬取哪些数据不可以被爬取。
  • www.taobao.com/robots.txt可以查看淘宝的robtos.txt协议

http协议

  • 概念:就是服务器和客户端进行数据交互的一种形式。
  • 官方概念:HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议。
  • 工作原理:
    • HTTP协议工作于客户端-服务端架构为上。浏览器作为HTTP客户端通过URL向HTTP服务端即WEB服务器发送所有请求。Web服务器根据接收到的请求后,向客户端发送响应信息。

常用请求头信息

             accept:浏览器通过这个头告诉服务器,它所支持的数据类型
        Accept-Charset: 浏览器通过这个头告诉服务器,它支持哪种字符集
        Accept-Encoding:浏览器通过这个头告诉服务器,支持的压缩格式
        Accept-Language:浏览器通过这个头告诉服务器,它的语言环境
        Host:浏览器通过这个头告诉服务器,想访问哪台主机
        If-Modified-Since: 浏览器通过这个头告诉服务器,缓存数据的时间
        Referer:浏览器通过这个头告诉服务器,客户机是哪个页面来的 防盗链
        Connection:浏览器通过这个头告诉服务器,请求完后是断开链接还是何持链接
        X-Requested-With: XMLHttpRequest 代表通过ajax方式进行访问
        User-Agent:请求载体的身份标识

常用响应头信息

                    Location: 服务器通过这个头,来告诉浏览器跳到哪里
            Server:服务器通过这个头,告诉浏览器服务器的型号
            Content-Encoding:服务器通过这个头,告诉浏览器,数据的压缩格式
            Content-Length: 服务器通过这个头,告诉浏览器回送数据的长度
            Content-Language: 服务器通过这个头,告诉浏览器语言环境
            Content-Type:服务器通过这个头,告诉浏览器回送数据的类型
            Refresh:服务器通过这个头,告诉浏览器定时刷新
            Content-Disposition: 服务器通过这个头,告诉浏览器以下载方式打数据
            Transfer-Encoding:服务器通过这个头,告诉浏览器数据是以分块方式回送的
            Expires: -1 控制浏览器不要缓存
            Cache-Control: no-cache 
            Pragma: no-cache

https协议

HTTPS (Secure Hypertext Transfer Protocol)安全超文本传输协议,HTTPS是在HTTP上建立SSL加密层,并对传输数据进行加密,是HTTP协议的安全版。

Python spiders基础学习笔记_第1张图片

https加密算法

**对称秘钥加密:**客户端向服务器发送一条信息,首先客户端会采用已知的算法对信息进行加密,比如MD5或者Base64加密,接收端对加密的信息进行解密的时候需要用到密钥,中间会传递密钥,(加密和解密的密钥是同一个),密钥在传输中间是被加密的。这种方式看起来安全,但是仍有潜在的危险,一旦被窃听,或者信息被挟持,就有可能破解密钥,而破解其中的信息。因此“共享密钥加密”这种方式存在安全隐患。

Python spiders基础学习笔记_第2张图片

非对称秘钥加密:“非对称加密”使用的时候有两把锁,一把叫做“私有密钥”,一把是“公开密钥”,使用非对象加密的加密方式的时候,服务器首先告诉客户端按照自己给定的公开密钥进行加密处理,客户端按照公开密钥加密以后,服务器接受到信息再通过自己的私有密钥进行解密,这样做的好处就是解密的钥匙根本就不会进行传输,因此也就避免了被挟持的风险。就算公开密钥被窃听者拿到了,它也很难进行解密,因为解密过程是对离散对数求值,这可不是轻而易举就能做到的事。以下是非对称加密原理图:

Python spiders基础学习笔记_第3张图片

但是非对称秘钥加密技术也存在如下缺点:

  • 第一个是:如何保证接收端向发送端发出公开秘钥的时候,发送端确保收到的是预先要发送的,而不会被挟持。只要是发送密钥,就有可能有被挟持的风险。
  • 第二个是:非对称加密的方式效率比较低,它处理起来更为复杂,通信过程中使用就有一定的效率问题而影响通信速度

**证书秘钥加密:**在上面我们讲了非对称加密的缺点,其中第一个就是公钥很可能存在被挟持的情况,无法保证客户端收到的公开密钥就是服务器发行的公开密钥。此时就引出了公开密钥证书机制。数字证书认证机构是客户端与服务器都可信赖的第三方机构。证书的具体传播过程如下:

  • 服务器的开发者携带公开密钥,向数字证书认证机构提出公开密钥的申请,数字证书认证机构在认清申请者的身份,审核通过以后,会对开发者申请的公开密钥做数字签名,然后分配这个已签名的公开密钥,并将密钥放在证书里面,绑定在一起
  • 服务器将这份数字证书发送给客户端,因为客户端也认可证书机构,客户端可以通过数字证书中的数字签名来验证公钥的真伪,来确保服务器传过来的公开密钥是真实的。一般情况下,证书的数字签名是很难被伪造的,这取决于认证机构的公信力。一旦确认信息无误之后,客户端就会通过公钥对报文进行加密发送,服务器接收到以后用自己的私钥进行解密。

Python spiders基础学习笔记_第4张图片

requests模块基础

  • 在python实现的网络爬虫中,用于网络请求发送的模块有两种,第一种为urllib模块,第二种为requests模块。urllib模块是一种比较古老的模块,在使用的过程中较为繁琐和不便。当requests模块出现后,就快速的代替了urllib模块,因此,在我们课程中,推荐大家使用requests模块。

  • Requests 唯一的一个非转基因的 Python HTTP 库,人类可以安全享用。

  • requests模块是python中原生的基于网络请求的模块,其主要作用是用来模拟浏览器发起请求。功能强大,用法简洁高效。在爬虫领域中占据着半壁江山的地位。

  • 环境安装

    • pip install requests
  • 使用流程/编码流程

    • 指定url
    • 基于requests模块发起请求
    • 获取响应对象中的数据值
    • 持久化存储

爬取搜狗首页的页面数据

import requests
if __name__ == "__main__":
    #step_1:指定url
    url = 'https://www.sogou.com/'
    #step_2:发起请求
    #get方法会返回一个响应对象
    response = requests.get(url=url)
    #step_3:获取响应数据.text返回的是字符串形式的响应数据
    page_text = response.text
    print(page_text)
    #step_4:持久化存储
    with open('./sogou.html','w',encoding='utf-8') as fp:
        fp.write(page_text)
    print('爬取数据结束!!!')

网页采集器

UA:User-Agent(请求载体的身份标识)
UA检测:门户网站的服务器会检测对应请求的载体身份标识,如果检测到请求的载体身份标识为某一款浏览器,
说明该请求是一个正常的请求。但是,如果检测到请求的载体身份标识不是基于某一款浏览器的,则表示该请求
为不正常的请求(爬虫),则服务器端就很有可能拒绝该次请求。

UA伪装:让爬虫对应的请求载体身份标识伪装成某一款浏览器

在sogou上搜索Mustang:
https://www.sogou.com/web?query=Mustang&_asf=www.sogou.com&_ast=&w=01019900&p=40040100&ie=utf8&from=index-nologin&s_from=index&sut=347184&sst0=1607063708245&lkt=7%2C1607063706415%2C1607063707871&sugsuv=1593752542573356&sugtime=1607063708245

定义url: https://www.sogou.com/web

import requests
if __name__ == "__main__":
    #UA伪装:将对应的User-Agent封装到一个字典中
    headers = {
     
        'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36'
    }
    url = 'https://www.sogou.com/web'
    #处理url携带的参数:封装到字典中
    kw = input('enter a word:')
    param = {
     
        'query':kw
    }
    #对指定的url发起的请求对应的url是携带参数的,并且请求过程中处理了参数
    response = requests.get(url=url,params=param,headers=headers)

    page_text = response.text
    fileName = kw+'.html'
    with open(fileName,'w',encoding='utf-8') as fp:
        fp.write(page_text)
    print(fileName,'Crawler is ok!!!')

破解百度翻译

在百度翻译输入框中输入一个字母就会实时的出现该字的翻译及类似相关的翻译,在Network中的Preview可以看见json格式化后的响应数据。在python中输入一个单词就可以获取它的翻译并保存下来

python代码:

import requests
import json
if __name__ == "__main__":
    #1.指定url
    post_url = 'https://fanyi.baidu.com/sug'
    #2.进行UA伪装
    headers = {
     
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36'

    }
    #3.post请求参数处理(同get请求一致)
    word = input('enter a word:')
    data = {
     
        'kw':word
    }
    #4.请求发送
    response = requests.post(url=post_url,data=data,headers=headers)
    #5.获取响应数据:json()方法返回的是obj(如果确认响应数据是json类型的,才可以使用json())
    dic_obj = response.json()

    #持久化存储
    fileName = word+'.json'
    with open(fileName,'w',encoding='utf-8') as fp:
        json.dump(dic_obj,fp=fp,ensure_ascii=False)

    print('over!!!')

豆瓣电影爬取

import requests
import json

if __name__ == '__main__':
    url = 'https://movie.douban.com/j/search_subjects'
    param = {
       #查询的参数
        'type': 'movie',  #查询的电影
        'tag': '恐怖',   #电影的类型是恐怖
        'sort': 'recommend', #推荐的
        'page_limit': '20', #一次取多少部电影
        'page_start': '0'  #从第几部位置开始取
    }
    headers = {
     
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36'
    }
    response = requests.get(url=url, params=param, headers=headers)
    list_data = response.json()
    print(list_data)
    with open('./douban.json', 'w', encoding='utf-8') as fp:
        #ensure_ascii,当它为True的时候,所有非ASCII码字符显示为\uXXXX序列,只需在dump时将ensure_ascii设置为False即可,此时存入json的中文即可正常显示。
        json.dump(list_data, fp=fp, ensure_ascii=False)
    print('Crawler is ok!')

药监总局相关数据爬取

需求:爬取国家药品监督管理总局中基于中华人民共和国化妆品生产许可证相关数据,http://scxk.nmpa.gov.cn:81/xk/
    - 动态加载数据
    - 首页中对应的企业信息数据是通过ajax动态请求到的。

    http://125.35.6.84:81/xk/itownet/portal/dzpz.jsp?id=e6c1aa332b274282b04659a6ea30430a
    http://125.35.6.84:81/xk/itownet/portal/dzpz.jsp?id=f63f61fe04684c46a016a45eac8754fe
    - 通过对详情页url的观察发现:
        - url的域名都是一样的,只有携带的参数(id)不一样
        - id值可以从首页对应的ajax请求到的json串中获取
        - 域名和id值拼接处一个完整的企业对应的详情页的url
    - 详情页的企业详情数据也是动态加载出来的
        - http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsById
        - http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsById
        - 观察后发现:
            - 所有的post请求的url都是一样的,只有参数id值是不同。
            - 如果我们可以批量获取多家企业的id后,就可以将id和url形成一个完整的详情页对应详情数据的ajax请求的url
import requests
import json

if __name__ == '__main__':
    #获取id的url
    post_url01 = 'http://scxk.nmpa.gov.cn:81/xk/itownet/portalAction.do?method=getXkzsList'
    #获取企业详细信息的url
    post_url02 = 'http://scxk.nmpa.gov.cn:81/xk/itownet/portalAction.do?method=getXkzsById'
    #UA伪装
    headers = {
     
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36'
    }
    # 存储企业的id
    id_list = []
    # 存储所有的企业详情数据
    data_list = []
    # 爬取1-5页
    for i in range(1,6):
        #参数的封装
        param01 = {
     
            'on': 'true',
            'page': str(i),
            'pageSize': '15',
            'productName': '',
            'conditionType': '1',
            'applyname': '',
            'applysn': ''
        }
        #返回json格式的响应数据
        page_text01 = requests.post(url=post_url01, data=param01, headers=headers).json()
        # 批量获取ID
        for id_a in page_text01["list"]:
            ID = id_a["ID"]
            id_list.append(ID)
    # 批量获取详细信息
    for id_b in id_list:
        param02 = {
     
            'id': id_b
        }
        page_text02 = requests.post(url=post_url02, data=param02, headers=headers).json()
        data_list.append(page_text02)
    #持久化存储
    with open('medical.json','w',encoding='utf-8') as fp:
        json.dump(data_list,fp=fp,ensure_ascii=False)

数据解析

聚焦爬虫:

爬取页面中指定的页面内容。

编码流程:

  1. 指定url
  2. 发起请求
  3. 获取响应数据
  4. 数据解析
  5. 持久化存储

数据解析分类:

  • 正则
  • bs4
  • xpath(***)

数据解析原理概述:

解析的局部的文本内容都会在标签之间或者标签对应的属性中进行存储

  • 1.进行指定标签的定位
  • 2.标签或者标签对应的属性中存储的数据值进行提取(解析)

正则

爬取单张图片

import requests

if __name__ == '__main__':
    #爬取图片数据
    url = 'https://pic.qiushibaike.com/system/pictures/12384/123840689/medium/YNPMVERXPFQZEAMA.jpg'
    #content返回的是二进制形式的图片数据
    # text(字符串) content(二进制)json() (对象)
    img_data = requests.get(url=url).content
    with open('qiutu01.jpg','wb') as fp:
        fp.write(img_data)

正则解析

<div class="thumb">
<a href="/article/123845932" target="_blank">
<img src="//pic.qiushibaike.com/system/pictures/12384/123845932/medium/0OVJD6UPJDMYLJ6O.jpg" alt="糗事#123845932" class="illustration" width="100%" height="auto">
a>
div>

python代码:

需求:爬取糗事百科中糗图板块一页下的所有的糗图图片

import requests
import re
import os

if __name__ == '__main__':
    #创建一个文件夹,保存所有的图片
    if not os.path.exists('./qiutuLibs'):
        os.mkdir('./qiutuLibs')
    
    url = 'https://www.qiushibaike.com/imgrank/'
    headers = {
     
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36'
    }
    #使用通用爬虫对url对应的一整张页面进行爬取
    page_text = requests.get(url=url,headers=headers).text
    #使用聚焦爬虫将页面中所有的糗图进行解析/提取
    ex = '
.*?' #使用re.S参数以后,正则表达式会将这个字符串作为一个整体,将“\n”当做一个普通的字符加入到这个字符串中,在整体中进行匹配。不然的话,这里会出现空列表 img_src_list = re.findall(ex,page_text,re.S) # print(img_src_list) for src in img_src_list: #拼接出一个完整的图片url src = 'https:'+src #请求到了图片的二进制数据 img_data =requests.get(url=src,headers=headers).content #生成图片名称 img_name = src.split('/')[-1] #图片存储的路径 imgPath = './qiutuLibs/'+img_name with open(imgPath,'wb') as fp: fp.write(img_data) print(img_name,'download success!!')

正则解析分页爬取

需求:爬取糗事百科中糗图板块1-3页下的所有的糗图图片

import requests
import re
import os

if __name__ == '__main__':
    if not os.path.exists('./qiutuLibs/all'):
        os.mkdir('./qiutuLibs/all')
    headers = {
     
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36'
    }
    for pageNum in range(1, 4):
        #对应页码的url
        url = f'https://www.qiushibaike.com/imgrank/page/{pageNum}/'
        page_text = requests.get(url=url, headers=headers).text
        ex = '
.*?' img_src_list = re.findall(ex, page_text, re.S) for src in img_src_list: src = 'https:' + src img_data = requests.get(url=src, headers=headers).content img_name = src.split('/')[-1] imgPath = './qiutuLibs/all/' + img_name with open(imgPath, 'wb') as fp: fp.write(img_data) print(img_name, 'download success!!')

bs4数据解析

bs4进行数据解析
    - 数据解析的原理:
        - 1.标签定位
        - 2.提取标签、标签属性中存储的数据值
    - bs4数据解析的原理:
        - 1.实例化一个BeautifulSoup对象,并且将页面源码数据加载到该对象中
        - 2.通过调用BeautifulSoup对象中相关的属性或者方法进行标签定位和数据提取
    - 环境安装:
        - pip install bs4
        - pip install lxml
    - 如何实例化BeautifulSoup对象:
        - from bs4 import BeautifulSoup
        - 对象的实例化:
            - 1.将本地的html文档中的数据加载到该对象中
                    fp = open('./test.html','r',encoding='utf-8')
                    soup = BeautifulSoup(fp,'lxml')
            - 2.将互联网上获取的页面源码加载到该对象中
                    page_text = response.text
                    soup = BeatifulSoup(page_text,'lxml')
        - 提供的用于数据解析的方法和属性:
            - soup.tagName:返回的是文档中第一次出现的tagName对应的标签
            - soup.find():
                - find('tagName'):等同于soup.div
                - 属性定位:
                    - soup.find('div',class_/id/attr='song')
            - soup.find_all('tagName'):返回符合要求的所有标签(列表)
        - select:
            - select('某种选择器(id,class,标签...选择器)'),返回的是一个列表。
            - 层级选择器:
                - soup.select('.tang > ul > li > a'):>表示的是一个层级
                - soup.select('.tang > ul a'):空格表示的多个层级
        - 获取标签之间的文本数据:
            - soup.a.text/string/get_text()
            - text/get_text():可以获取某一个标签中所有的文本内容
            - string:只可以获取该标签下面直系的文本内容
        - 获取标签中属性值:
            - soup.a['href']

bs4解析基础

test.html文档

<html lang="en">
<head>
   <meta charset="UTF-8" />
   <title>测试bs4title>
head>
<body>
   <div>
      <p>百里守约p>
   div>
   <div class="song">
      <p>李清照p>
      <p>王安石p>
      <p>苏轼p>
      <p>柳宗元p>
      <a href="http://www.song.com/" title="赵匡胤" target="_self">
         <span>this is spanspan>
      宋朝是最强大的王朝,不是军队的强大,而是经济很强大,国民都很有钱a>
      <a href="" class="du">总为浮云能蔽日,长安不见使人愁a>
      <img src="http://www.baidu.com/meinv.jpg" alt="" />
   div>
   <div class="tang">
      <ul>
         <li><a href="http://www.baidu.com" title="qing">清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村a>li>
         <li><a href="http://www.163.com" title="qin">秦时明月汉时关,万里长征人未还,但使龙城飞将在,不教胡马度阴山a>li>
         <li><a href="http://www.126.com" alt="qi">岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君a>li>
         <li><a href="http://www.sina.com" class="du">杜甫a>li>
         <li><a href="http://www.dudu.com" class="du">杜牧a>li>
         <li><b>杜小月b>li>
         <li><i>度蜜月i>li>
         <li><a href="http://www.haha.com" id="feng">凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘a>li>
      ul>
   div>
body>
html>
from bs4 import BeautifulSoup

if __name__ == '__main__':
    # 将本地的html文档中的数据加载到该对象中
    with open('./test.html','r',encoding='utf-8') as fp:
        content = fp.read()
    soup = BeautifulSoup(content,'lxml') #使用lxml解析器进行解析content内容
    # print(soup) # 返回整个页面
    # print(soup.a)  #soup.tagName 返回的是html中第一次出现的tagName标签
    # print(soup.div)
    # find('tagName'):等同于soup.div
    # print(soup.find('div'))   #print(soup.div)
    # print(soup.find('div',class_='song').string)  # None
    # print(soup.find_all('a'))   #找到所有的a标签
    # print(soup.find('div',class_='tang').text) #返回div标签包括tang类选择器的所有文本内容
    # print(soup.select('.tang'))  #返回一个列表,包括tang类选择器所有的标签及文本内容
    # print(soup.select('.tang > ul a')[0]['href']) #取第一个a标签对应的href属性所对应的值,即url

bs4案例

需求:爬取三国演义小说所有的章节标题和章节内容http://www.shicimingju.com/book/sanguoyanyi.html

import requests
from bs4 import BeautifulSoup

if __name__ == '__main__':
    headers = {
     
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36'
    }
    #对首页的页面数据进行爬取
    url = 'https://www.shicimingju.com/book/sanguoyanyi.html'
    page_text = requests.get(url=url, headers=headers).text
    #在首页中解析出章节的标题和详情页的url
    #1.实例化BeautifulSoup对象,需要将页面源码数据加载到该对象中
    soup = BeautifulSoup(page_text, 'lxml')
    #2.定位li标签
    li_list = soup.select('.book-mulu>ul>li')
	#3.循环遍历li标签
    for li in li_list:
        #4. 获取a标签的文本内容,及标题
        title = li.a.string
        #5. 获取标题对应的url地址
        detail_url = 'https://www.shicimingju.com' + li.a['href']
        #6. 向文章内容的页面发起请求
        detail_ret = requests.get(url=detail_url, headers=headers).text
        #7. 实例化BeautifulSoup类,使用lxml解析器进行解析文章内容的页面detail_ret
        detail_soup = BeautifulSoup(detail_ret, 'lxml')
        #8. 找到文章内容对应的div标签
        div_tag = detail_soup.find('div', class_='chapter_content')
        #9. 获取文章内容
        detail_content = div_tag.text
        #10. 持久化存储
        with open('./sanguo.txt','a',encoding='utf-8') as fp:
            fp.write(title+':'+detail_content+'\n')
        print(title,'Crawler is ok!')

xpath解析

基础

最常用且最便捷高效的一种解析方式,通用性。

- xpath解析原理:
    - 1.实例化一个etree的对象,且需要将被解析的页面源码数据加载到该对象中。
    - 2.调用etree对象中的xpath方法结合着xpath表达式实现标签的定位和内容的捕获。
- 环境的安装:
    - pip install lxml
- 如何实例化一个etree对象:
	from lxml import etree
    - 1.将本地的html文档中的源码数据加载到etree对象中:
        etree.parse(filePath)
    - 2.可以将从互联网上获取的源码数据加载到该对象中
        etree.HTML('page_text')
    - xpath('xpath表达式')
- xpath表达式:
    - /:表示的是从根节点开始定位。表示的是一个层级。
    - //:表示的是多个层级。可以表示从任意位置开始定位。
    - 属性定位://div[@class='song'] tag[@attrName="attrValue"]
    - 索引定位://div[@class="song"]/p[3] 索引是从1开始的。
    - 取文本:
        - /text() 获取的是标签中直系的文本内容
        - //text() 标签中非直系的文本内容(所有的文本内容)
    - 取属性:
        /@attrName     ==>img/@src

基础练习:

from lxml import etree

if __name__ == '__main__':
    # 实例化好了一个etree对象,且将被解析的源码加载到了该对象中
    tree = etree.parse('./test.html')
    # r = tree.xpath('/html/body/div') #返回的是一个列表
    # r=tree.xpath('/html//div')
    # r=tree.xpath('//div')
    # r=tree.xpath('//div[@class="song"]')
    # r=tree.xpath('//div[@class="song"]/p')
    # r=tree.xpath('//div[@class="tang"]//li[5]/a/text()')[0]
    # r=tree.xpath('//li[7]//text()')[0]
    # r=tree.xpath('//div[@class="tang"]//text()')
    r=tree.xpath('//div[@class="song"]/img/@src')

    print(r)

xpath解析案例-58二手房

爬取58二手房中的房源信息

import requests
from lxml import etree

if __name__ == '__main__':
    url = 'https://bj.58.com/ershoufang/'
    headers = {
     
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36'
    }
    #爬取到页面源码数据
    page_text = requests.get(url=url, headers=headers).text
	#数据解析
    tree = etree.HTML(page_text)
    #存储的就是li标签对象
    select_data = tree.xpath('//ul[@class="house-list-wrap"]/li')
    fp = open('ershoufang.txt', 'w', encoding='utf-8')
    for se_data in select_data:
        #局部解析
        se_data = se_data.xpath('./div[2]/h2/a/text()')[0]
        fp.write(se_data+'\n')
        print(se_data + 'download success!')
    fp.close()

Python spiders基础学习笔记_第5张图片

xpath解析案例-4k图片解析爬取

解析下载图片数据 http://pic.netbian.com/4kmeinv/

爬取首页:

import requests
from lxml import etree
import os

if __name__ == '__main__':
    #创建一个文件夹
    if not os.path.exists('./meinv'):
        os.mkdir('./meinv')
    url = 'http://pic.netbian.com/4kmeinv/index.html'
    headers = {
     
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36'
    }
    response = requests.get(url=url, headers=headers)
    #手动设定响应数据的编码格式,有可能没用,则用下面的方法
    # response.encoding = 'utf-8'
    page_text = response.text
    tree = etree.HTML(page_text)
    li_list = tree.xpath('//ul[@class="clearfix"]/li')
    for li in li_list:
        #数据解析:src的属性值  alt属性
        img = li.xpath('./a/img/@src')[0]
        img_url = 'http://pic.netbian.com' + img
        #请求图片,二进制的形式
        img_data = requests.get(url=img_url, headers=headers).content
        img_name = li.xpath('./a/img/@alt')[0] + '.jpg'
        #通用处理中文乱码的解决方案
        img_name = img_name.encode('iso-8859-1').decode('gbk')
        #请求图片进行持久化存储
        img_fileName = './meinv/' + img_name
        with open(img_fileName, 'wb') as f:
            f.write(img_data)
            print(img_name + '----------download successfully!!')

爬取2-7页

import requests
from lxml import etree
import os

if __name__ == '__main__':
    if not os.path.exists('./meinv'):
        os.mkdir('./meinv')
    url = 'http://pic.netbian.com/4kmeinv/index.html'
    headers = {
     
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36'
    }
    for i in range(2,8):
        next_url = f'http://pic.netbian.com/4kmeinv/index_{i}.html'
        page_text = requests.get(url=next_url, headers=headers).text

        tree = etree.HTML(page_text)
        li_list = tree.xpath('//ul[@class="clearfix"]/li')
        for li in li_list:
            img = li.xpath('./a/img/@src')[0]
            img_url = 'http://pic.netbian.com' + img
            img_data = requests.get(url=img_url, headers=headers).content
            img_name = li.xpath('./a/img/@alt')[0] + '.jpg'
            img_name = img_name.encode('iso-8859-1').decode('gbk')
            img_fileName = './meinv/' + img_name
            with open(img_fileName, 'wb') as f:
                f.write(img_data)
                print(img_name + '----------download successfully!!')

xpath解析案例-全国城市名称爬取

需求:解析出所有城市名称https://www.aqistudy.cn/historydata/

# 写法一:不推荐
import requests
from lxml import etree

if __name__ == '__main__':
    url = 'https://www.aqistudy.cn/historydata/'
    headers = {
     
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36'
    }
    page_text = requests.get(url=url,headers=headers).text

    tree = etree.HTML(page_text)
    #解析到了热门城市的城市名称
    a_content = tree.xpath('//div[@class="hot"]//a/text()')
    fp = open('city.txt','w',encoding='utf-8')
    for city in a_content:
        fp.write(city+'\n')
        print(f'获取了城市: {city}')
    # fp.close()
    #获取一般城市的城市名称
    a_content_ = tree.xpath('//div[@class="all"]//a/text()')
    for city_ in a_content_:
        fp.write(city_+'\n')
        print(f'获取了城市: {city_}')
    fp.close()


# 写法二:推荐
import requests
from lxml import etree

if __name__ == '__main__':
    url = 'https://www.aqistudy.cn/historydata/'
    headers = {
     
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36'
    }
    page_text = requests.get(url=url, headers=headers).text

    tree = etree.HTML(page_text)
    # 解析到热门城市和所有城市对应的a标签
    # '//div[@class="hot"]//a/text()'   热门城市
    # '//div[@class="all"]//a/text()'   所有城市
    # 合并 | 写在一起
    a_content = tree.xpath('//div[@class="hot"]//a/text() | //div[@class="all"]//a/text()')
    fp = open('city.txt', 'w', encoding='utf-8')
    for city in a_content:
        fp.write(city + '\n')
        print(f'获取了城市: {city}')
    fp.close()

Python spiders基础学习笔记_第6张图片

验证码

验证码识别

验证码和爬虫之间的爱恨情仇?
反爬机制:验证码.识别验证码图片中的数据,用于模拟登陆操作。

识别验证码的操作:
    - 人工肉眼识别。(不推荐)
    - 第三方自动识别(推荐)
        - 超级鹰:http://www.chaojiying.com/api.html
超级鹰的使用流程:
    - 注册:用户中心或软件开发商
    - 登录:
        - 进入用户中心充2块人民币
        - 软件ID 生成一个用来接入接口的软件ID/如果是软件开发商,就去开发商中心生成软件ID
        - 参考开发文档,下载python示例代码
        - 解压代码,复制到pycharm当前文件夹
        - 修改代码中的一些错误参数:https://blog.csdn.net/Lingguo_0921/article/details/108652286
实战:识别古诗文网登录页面中的验证码。
使用超级鹰平台识别验证码的编码流程:
    - 将验证码图片进行本地下载
    - 调用平台提供的示例代码进行图片数据识别

解压并修改后的代码:

#!/usr/bin/env python
# coding:utf-8

import requests
from hashlib import md5


class Chaojiying_Client(object):

    def __init__(self, username, password, soft_id):
        self.username = username

        self.password = md5(password.encode("utf-8")).hexdigest()
        self.soft_id = soft_id
        self.base_params = {
     
            'user': self.username,
            'pass2': self.password,
            'softid': self.soft_id,
        }
        self.headers = {
     
            'Connection': 'Keep-Alive',
            'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)',
        }

    def PostPic(self, im, codetype):
        """
        im: 图片字节
        codetype: 题目类型 参考 http://www.chaojiying.com/price.html
        """
        params = {
     
            'codetype': codetype,
        }
        params.update(self.base_params)
        files = {
     'userfile': ('ccc.jpg', im)}
        r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files,
                          headers=self.headers)
        return r.json()

    def ReportError(self, im_id):
        """
        im_id:报错题目的图片ID
        """
        params = {
     
            'id': im_id,
        }
        params.update(self.base_params)
        r = requests.post('http://upload.chaojiying.net/Upload/ReportError.php', data=params, headers=self.headers)
        return r.json()


if __name__ == '__main__':
    chaojiying = Chaojiying_Client('超级鹰用户名', '超级鹰用户名的密码', '96001')  # 用户中心>>软件ID 生成一个替换 96001
    im = open('a.jpg', 'rb').read()  # 本地图片文件路径 来替换 a.jpg 有时WIN系统须要//
    print(chaojiying.PostPic(im, 1902))  # 1902 验证码类型  官方网站>>价格体系 3.4+版 print 后要加()

把上面这个python文件分成两部分:

第一部分作为被导入其它文件的模块:取名为CodeClass.py

#!/usr/bin/env python
# coding:utf-8

import requests
from hashlib import md5


class Chaojiying_Client(object):

    def __init__(self, username, password, soft_id):
        self.username = username

        self.password = md5(password.encode("utf-8")).hexdigest()
        self.soft_id = soft_id
        self.base_params = {
     
            'user': self.username,
            'pass2': self.password,
            'softid': self.soft_id,
        }
        self.headers = {
     
            'Connection': 'Keep-Alive',
            'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)',
        }

    def PostPic(self, im, codetype):
        """
        im: 图片字节
        codetype: 题目类型 参考 http://www.chaojiying.com/price.html
        """
        params = {
     
            'codetype': codetype,
        }
        params.update(self.base_params)
        files = {
     'userfile': ('ccc.jpg', im)}
        r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files,
                          headers=self.headers)
        return r.json()

    def ReportError(self, im_id):
        """
        im_id:报错题目的图片ID
        """
        params = {
     
            'id': im_id,
        }
        params.update(self.base_params)
        r = requests.post('http://upload.chaojiying.net/Upload/ReportError.php', data=params, headers=self.headers)
        return r.json()

第二部分及主函数后面的,用来在验证验证码的文件当中参数

if __name__ == '__main__':
    chaojiying = Chaojiying_Client('超级鹰用户名', '超级鹰用户名的密码', '96001')  # 用户中心>>软件ID 生成一个替换 96001
    im = open('a.jpg', 'rb').read()  # 本地图片文件路径 来替换 a.jpg 有时WIN系统须要//
    print(chaojiying.PostPic(im, 1902))  # 1902 验证码类型  官方网站>>价格体系 3.4+版 print 后要加()

古诗文网验证码识别

import requests
from lxml import etree
from CodeClass import Chaojiying_Client

if __name__ == '__main__':
    url = 'https://so.gushiwen.org/user/login.aspx?from=http://so.gushiwen.org/user/collect.aspx'
    headers = {
     
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36'
    }
	# 将验证码图片下载到本地
    page_text = requests.get(url=url,headers=headers).text
    # 解析验证码图片img中src属性值
    tree = etree.HTML(page_text)
    code_img_src = 'https://so.gushiwen.org'+tree.xpath('//*[@id="imgCode"]/@src')[0]
    img_data = requests.get(url=code_img_src,headers=headers).content
    # 将验证码图片保存到了本地
    with open('./code.jpg','wb') as fp:
        fp.write(img_data)

    # 调用打码平台的示例程序进行验证码图片数据识别
    # Chaojiying_Client传入的参数依次是:登录超级鹰的用户名,登录超级鹰的密码,在超级鹰中生成的软件id
    chaojiying = Chaojiying_Client('xxxxx', 'xxxx', 'xxxxx')
    im = open('code.jpg', 'rb').read()
    print(chaojiying.PostPic(im, 1902))

效果:

Python spiders基础学习笔记_第7张图片

successfully!!!

requests模块高级

模拟登录:
    - 爬取基于某些用户的用户信息。
需求:对人人网进行模拟登录。
    - 点击登录按钮之后会发起一个post请求
    - post请求中会携带登录之前录入的相关的登录信息(用户名,密码,验证码......)
    - 验证码:每次请求都会变化

需求:爬取当前用户的相关的用户信息(个人主页中显示的用户信息)

http/https协议特性:无状态。
没有请求到对应页面数据的原因:
    发起的第二次基于个人主页页面请求的时候,服务器端并不知道该此请求是基于登录状态下的请求。
cookie:用来让服务器端记录客户端的相关状态。
    - 手动处理:通过抓包工具获取cookie值,将该值封装到headers中。(不建议)
    - 自动处理:
        - cookie值的来源是哪里?
            - 模拟登录post请求后,由服务器端创建。
        session会话对象:
            - 作用:
                1.可以进行请求的发送。
                2.如果请求过程中产生了cookie,则该cookie会被自动存储/携带在该session对象中。
        - 创建一个session对象:session = requests.Session()
        - 使用session对象进行模拟登录post请求的发送(cookie就会被存储在session中)
        - session对象对个人主页对应的get请求进行发送(携带了cookie)

代理:破解封IP这种反爬机制。
什么是代理:
    - 代理服务器。
代理的作用:
    - 突破自身IP访问的限制。
    - 隐藏自身真实IP
代理相关的网站:
    - 快代理
    - 西祠代理
    - www.goubanjia.com
代理ip的类型:
    - http:应用到http协议对应的url中
    - https:应用到https协议对应的url中

代理ip的匿名度:
    - 透明:服务器知道该次请求使用了代理,也知道请求对应的真实ip
    - 匿名:知道使用了代理,不知道真实ip
    - 高匿:不知道使用了代理,更不知道真实的ip

模拟登录人人网

#编码流程:
#1.验证码的识别,获取验证码图片的文字数据
#2.对post请求进行发送(处理请求参数)
#3.对响应数据进行持久化存储

import requests
from lxml import etree
from CodeClass import Chaojiying_Client

if __name__ == '__main__':

    url = 'http://www.renren.com/SysHome.do'
    headers = {
     
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36'
    }
    # 获取整个页面
    page_text = requests.get(url=url,headers=headers).text

    # 使用xpath进行解析获取验证码图片url
    tree = etree.HTML(page_text)
    img_url = tree.xpath('//*[@id="verifyPic_login"]/@src')[0]
    print(img_url)

    # 请求图片url并进行下载存储
    img_pic = requests.get(url=img_url,headers=headers).content
    with open('yzm.jpg','wb') as fp:
        fp.write(img_pic)
        print('yzm.jpg download successfully!')

    # 验证识别验证码
    chaojiying = Chaojiying_Client('xxxxx', 'xxxx', 'xxxx')
    im = open('yzm.jpg', 'rb').read()
    result = chaojiying.PostPic(im, 1902)['pic_str']
    print('验证码的识别结果:',result)

    # post请求的发送(模拟登录)
    login_url = 'http://www.renren.com/ajaxLogin/login?1=1&uniqueTimestamp=20201101516462'
    data = {
     
        'email': '[email protected]',
        'icode': result,
        'origURL': 'http://www.renren.com/home',
        'domain': 'renren.com',
        'key_id': '1',
        'captcha_type': 'web_login',
        'password': '803762eddbba49f5f6b762240b311ae5bfa4bcce70627231dd1f08b9c7c6f5376',
        'rkey': '1028219f2897941c98abdc0839a729df',
 		'f':'',
    }
    response = requests.post(url=login_url, headers=headers, data=data)
    print(response.text)
    print(response.status_code)  # 打印状态码或者持久化存储

爬取人人网当前用户的个人详情页数据

当我们用上面相同的方法去爬取用户个人详情页数据的时候,发现失败,那是因为没有cookie,而浏览器有。所以浏览器在登录了网站过后,去访问当前网站的其它页面可以不同再去登录,而pycharm没有,所以,我们要给pycharm加cookie。

import requests
from lxml import etree
from CodeClass import Chaojiying_Client

if __name__ == '__main__':

    url = 'http://www.renren.com/SysHome.do'
    headers = {
     
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36'
    }
    # 获取整个页面
    page_text = requests.get(url=url,headers=headers).text

    # 使用xpath进行解析获取验证码图片url
    tree = etree.HTML(page_text)
    img_url = tree.xpath('//*[@id="verifyPic_login"]/@src')[0]
    print(img_url)

    # 请求图片url并进行下载存储
    img_pic = requests.get(url=img_url,headers=headers).content
    with open('yzm.jpg','wb') as fp:
        fp.write(img_pic)
        print('yzm.jpg download successfully!')

    # 验证识别验证码
    chaojiying = Chaojiying_Client('xxx', 'xxxx', 'xxx')
    im = open('yzm.jpg', 'rb').read()
    result = chaojiying.PostPic(im, 1902)['pic_str']
    print('验证码的识别结果:',result)

    # post请求的发送(模拟登录)
    login_url = 'http://www.renren.com/ajaxLogin/login?1=1&uniqueTimestamp=20201101516462'
    data = {
     
        'email': '[email protected]',
        'icode': result,
        'origURL': 'http://www.renren.com/home',
        'domain': 'renren.com',
        'key_id': '1',
        'captcha_type': 'web_login',
        'password': '803762eddbba49f5f6b762240b311ae5bfa4bcce70627231dd1f08b9c7c6f5376',
        'rkey': '1028219f2897941c98abdc0839a729df',
        'f': 'https%3A%2F%2Fwww.baidu.com%2Flink%3Furl%3Dgds6TUs9Q1ojOatGda5mVsLKC34AYwc5XiN8OuImHRK%26wd%3D%26eqid%3D8e38ba9300429d7d000000035cedf53a',
    }
    # 创建一个session对象
    session = requests.Session()
    # 使用session进行post请求的发送
    response = session.post(url=login_url, headers=headers, data=data)
    print(response.status_code)
    # 不再用下面这种request.post方式
    # response = requests.post(url=login_url, headers=headers, data=data)
    # print(response.text)
    # print(response.status_code)  # 打印状态码

    # 爬取当前用户的个人主页对应的页面数据,不使用session的话,这个页面访问不到
    detail_url = 'http://www.renren.com/409996207/profile'
    # 手动cookie处理,不推荐这种方式
    # headers = {
     
    #     'Cookie':'xxxx'
    # }

    # 使用携带cookie的session进行get请求的发送,推荐这种方式
    detail_page_text = session.get(url=detail_url, headers=headers).text
    with open('bobo.html', 'w', encoding='utf-8') as fp:
        fp.write(detail_page_text)

古诗文网模拟登录

import requests
from lxml import etree
from CodeClass import Chaojiying_Client

if __name__ == '__main__':
    # 古诗文登录页面
    url = 'https://so.gushiwen.org/user/login.aspx?from=http://so.gushiwen.org/user/collect.aspx'
    # 将验证码图片下载到本地
    headers = {
     
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36'
    }
    # 获取古诗文登录页面
    page_text = requests.get(url=url,headers=headers).text
    #解析验证码图片img中src属性值
    tree = etree.HTML(page_text)
    code_img_src = 'https://so.gushiwen.org'+tree.xpath('//*[@id="imgCode"]/@src')[0]
    # 将验证码图片保存到了本地
    img_data = requests.get(url=code_img_src,headers=headers).content
    with open('./yzm.jpg','wb') as fp:
        fp.write(img_data)

    #调用打码平台的示例程序进行验证码图片数据识别
    chaojiying = Chaojiying_Client('kevin886869', '^Kevin886869$', '910475')
    im = open('yzm.jpg', 'rb').read()
    result = chaojiying.PostPic(im, 1902)['pic_str']
    print('验证码的识别结果:',result)

    # 进行带cookie模拟登录
    # 解析动态参数
    __VIEWSTATE = tree.xpath('//*[@id="__VIEWSTATE"]/@value')[0]
    __VIEWSTATEGENERATOR = tree.xpath('//*[@id="__VIEWSTATEGENERATOR"]/@value')[0]

    param = {
     
    # 前两个参数是动态参数
    '__VIEWSTATE': __VIEWSTATE,
    '__VIEWSTATEGENERATOR': __VIEWSTATEGENERATOR,
    'from': 'http://so.gushiwen.org/user/collect.aspx',
    'email': '[email protected]',
    'pwd': '^Kevin886869$',
    'code': result ,
    'denglu': '登录'
    }
    # 模拟登录成功的url
    detail_url = 'https://so.gushiwen.org/user/login.aspx?from=http%3a%2f%2fso.gushiwen.org%2fuser%2fcollect.aspx'
    session = requests.Session()
    response = session.post(url=detail_url,headers=headers,data=param)
    print(response.status_code)
    detail_page = response.text

    with open('gsw.html','w',encoding='utf-8') as fp:
        fp.write(detail_page)

代理操作

import requests

url = 'https://www.baidu.com/s?tn=02003390_43_hao_pg&isource=infinity&iname=baidu&itype=web&ie=utf-8&wd=ip'
headers = {
     
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36'
}
page_text = requests.get(url=url,headers=headers).text
with open('ip.html','w',encoding='utf-8') as fp:
    fp.write(page_text)

    
    
import requests

url = 'https://www.baidu.com/s?tn=02003390_43_hao_pg&isource=infinity&iname=baidu&itype=web&ie=utf-8&wd=ip'
headers = {
     
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36'
}
page_text = requests.get(url=url, headers=headers, proxies={
     'https': '123.160.97.242:9999'}).text
with open('ip.html', 'w', encoding='utf-8') as fp:
    fp.write(page_text)

#反爬机制:  封ip
#反反爬策略:使用代理进行请求发送高性能异步爬虫

高性能异步爬虫

高性能异步爬虫
目的:在爬虫中使用异步实现高性能的数据爬取操作。

异步爬虫的方式:
    - 1.多线程,多进程(不建议):
        好处:可以为相关阻塞的操作单独开启线程或者进程,阻塞操作就可以异步执行。
        弊端:无法无限制的开启多线程或者多进程。
    - 2.线程池、进程池(适当的使用):
        好处:我们可以降低系统对进程或者线程创建和销毁的一个频率,从而很好的降低系统的开销。
        弊端:池中线程或进程的数量是有上限。

- 3.单线程+异步协程(推荐):
    event_loop:事件循环,相当于一个无限循环,我们可以把一些函数注册到这个事件循环上,
    当满足某些条件的时候,函数就会被循环执行。

    coroutine:协程对象,我们可以将协程对象注册到事件循环中,它会被事件循环调用。
    我们可以使用 async 关键字来定义一个方法,这个方法在调用时不会立即被执行,而是返回
    一个协程对象。

    task:任务,它是对协程对象的进一步封装,包含了任务的各个状态。

    future:代表将来执行或还没有执行的任务,实际上和 task 没有本质区别。

    async 定义一个协程.

    await 用来挂起阻塞方法的执行。

同步爬虫

import requests

if __name__ == '__main__':
    headers = {
     
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36'
    }
    urls = [
        'http://pic.netbian.com/uploads/allimg/170512/215203-14945971236e27.jpg',
        'http://pic.netbian.com/uploads/allimg/170509/183947-1494326387e1c8.jpg',
        'http://pic.netbian.com/uploads/allimg/170522/180452-14954474926fd2.jpg',
    ]

    def get_content(url):
        print('正在爬取:',url)
        # get方法是一个阻塞的方法
        response = requests.get(url=url,headers=headers)
        if response.status_code == 200 :
            return response.content
    def content_len(content):
        print('当前图片的长度是:',len(content))
    for url in urls:
        content = get_content(url)
        content_len(content)

'''
正在爬取: http://pic.netbian.com/uploads/allimg/170512/215203-14945971236e27.jpg
当前图片的长度是: 122282
正在爬取: http://pic.netbian.com/uploads/allimg/170509/183947-1494326387e1c8.jpg
当前图片的长度是: 96561
正在爬取: http://pic.netbian.com/uploads/allimg/170522/180452-14954474926fd2.jpg
当前图片的长度是: 134977
'''

可以看见上面的结果是等到一张图片爬取完成过后再爬取下一张的,这样效率就低了。下面要做的事情就是让他们几乎同时执行。

线程池基本使用

详情可参考:

import requests
#导入线程池模块对应的类
from concurrent.futures import ThreadPoolExecutor

if __name__ == '__main__':
    headers = {
     
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36'
    }
    urls = [
        'http://pic.netbian.com/uploads/allimg/170512/215203-14945971236e27.jpg',
        'http://pic.netbian.com/uploads/allimg/170509/183947-1494326387e1c8.jpg',
        'http://pic.netbian.com/uploads/allimg/170522/180452-14954474926fd2.jpg',
    ]

    def get_content(url):
        print('正在爬取:',url)
        # get方法是一个阻塞的方法
        response = requests.get(url=url,headers=headers).content
        print('当前图片的长度是:',len(response))


    # 使用线程池方式执行,实例化一个线程池对象,创建一个大小为3的线程池
    t_pool = ThreadPoolExecutor(max_workers=3)
    # 将列表中每一个列表元素传递给get_content进行处理。
    t_pool.map(get_content,urls)
    # join住主线程,并关闭线程池
    t_pool.shutdown(True) 

线程池在爬虫案例中的应用

协程

本质就是一条单线程,在阻塞的任务之间来回切换,提高效率,实现并发

基本使用

import asyncio

async def request(url):
    print('正在请求的url是:',url)
    print('请求成功:',url)
    return url
#async修饰的函数,调用之后返回的一个协程对象
c = request('www.baidu.com')

# 创建一个事件循环对象
# loop = asyncio.get_event_loop()
# 将协程对象注册到loop中,然后启动loop
# loop.run_until_complete(c)
'''
正在请求的url是: www.baidu.com
请求成功: www.baidu.com
'''
# ret = loop.run_until_complete(c)
# print(ret)
'''
正在请求的url是: www.baidu.com
请求成功: www.baidu.com
www.baidu.com
'''

# task的使用
# loop = asyncio.get_event_loop()
# 基于loop创建了一个task对象
# task = loop.create_task(c)
# loop.run_until_complete(task)
# print(task.result())
'''
正在请求的url是: www.baidu.com
请求成功: www.baidu.com
www.baidu.com
'''

# future的使用
# loop = asyncio.get_event_loop()
# future = asyncio.ensure_future(c)
# loop.run_until_complete(future)
# print(future.result())
'''
正在请求的url是: www.baidu.com
请求成功: www.baidu.com
www.baidu.com
'''

def get_collback(future):
    # result返回的就是任务对象中封装的协程对象对应函数的返回值
    print(future.result())

# 绑定回调
loop = asyncio.get_event_loop()
# 对协程对象c的进一步封装,得到future对象
future = asyncio.ensure_future(c)
# 将回调函数绑定到任务对象中
future.add_done_callback(get_collback)
loop.run_until_complete(future)
print(future.result())
'''
正在请求的url是: www.baidu.com
请求成功: www.baidu.com
www.baidu.com
www.baidu.com
'''

多任务异步协程

# 不使用协程的情况
import time

def requests(url):
    print('正在下载: ',url)
    time.sleep(2)
    print('下载完毕: ',url)
start_time = time.time() 
urls = [
    'www.baidu.com',
    'www.sogou.com',
    'www.goubanjia.com'
]
for url in urls:
    requests(url)
print(time.time()-start_time)
'''
正在下载:  www.baidu.com
下载完毕:  www.baidu.com
正在下载:  www.sogou.com
下载完毕:  www.sogou.com
正在下载:  www.goubanjia.com
下载完毕:  www.goubanjia.com
6.001394510269165
'''


# 使用协程的情况
import asyncio
import time

async def requests(url):
    print('正在下载: ',url)
    # 在异步协程中如果出现了同步模块相关的代码,那么就无法实现异步。
    # time.sleep(2)  time.sleep()函数会将整个线程休眠几秒
    # 当在asyncio中遇到阻塞操作必须进行手动挂起
    await asyncio.sleep(2)  # asyncio.sleep()其实也是一个协程,这个协程将和事件循环直接通信并将一个Future对象交给事件循环,事件循环会一直监视着它直到它的任务完成(在这里就是休眠两秒),并不会将整个线程都停止执行。
    print('下载完毕: ',url)
start_time = time.time() # 返回当前系统时间
urls = [
    'www.baidu.com',
    'www.sogou.com',
    'www.goubanjia.com'
]
#任务列表:存放多个任务对象
stacks = []
for url in urls:
    c = requests(url)  # 创建协程对象
    task = asyncio.ensure_future(c)  # 创建future对象
    stacks.append(task)

# 创建事件循环对象loop
loop = asyncio.get_event_loop()
# 需要将任务列表封装到wait中,执行多个任务要使用asyncio.wait()
loop.run_until_complete(asyncio.wait(stacks))
print(time.time() - start_time)
'''
正在下载:  www.baidu.com
正在下载:  www.sogou.com
正在下载:  www.goubanjia.com
下载完毕:  www.baidu.com
下载完毕:  www.sogou.com
下载完毕:  www.goubanjia.com
2.0029125213623047
'''

aiohttp模块引入

# 下面是异步协程失败的例子

import asyncio
import requests

if __name__ == '__main__':
    urls = [
        'https://blog.csdn.net/qq_44788449/category_10241741.html',
        'https://blog.csdn.net/qq_44788449/category_10578487.html',
        'https://blog.csdn.net/qq_44788449/category_10052064.html'
    ]
    headers = {
     
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36'
    }

    async def page(url):
        print('正在下载页面:', url)
        # requests.get是基于同步,必须使用基于异步的网络请求模块进行指定url的请求发送
        # aiohttp:基于异步网络请求的模块
        response = requests.get(url=url, headers=headers)
        page_text = response.text
        print(f'下载状态:{response.status_code} ok' )

    stacks = []
    for url in urls:
        c = page(url)
        task = asyncio.ensure_future(c)
        stacks.append(task)
    loop = asyncio.get_event_loop()
    loop.run_until_complete(asyncio.wait(stacks))
'''
正在下载页面: https://blog.csdn.net/qq_44788449/category_10241741.html
下载状态:200 ok
正在下载页面: https://blog.csdn.net/qq_44788449/category_10578487.html
下载状态:200 ok
正在下载页面: https://blog.csdn.net/qq_44788449/category_10052064.html
下载状态:200 ok
'''

aiohttp模块实现多任务异步协程

环境安装:pip install aiohttp
使用该模块中的ClientSession

import asyncio
import requests
import aiohttp

if __name__ == '__main__':
    urls = [
        'https://blog.csdn.net/qq_44788449/category_10241741.html',
        'https://blog.csdn.net/qq_44788449/category_10578487.html',
        'https://blog.csdn.net/qq_44788449/category_10052064.html'
    ]
    headers = {
     
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36'
    }

    async def page(url):
        print('正在下载页面:', url)
        async with aiohttp.ClientSession() as session:
            async with await session.get(url=url, headers=headers) as response:
                # text()返回字符串形式的响应数据
                # read()返回的二进制形式的响应数据
                # json()返回的就是json对象
                # 注意:获取响应数据操作之前一定要使用await进行手动挂起
                page_text = await response.text()
                print(f'下载成功:{page_text}')


    stacks = []
    for url in urls:
        c = page(url)
        task = asyncio.ensure_future(c)
        stacks.append(task)
    loop = asyncio.get_event_loop()
    loop.run_until_complete(asyncio.wait(stacks))

Python spiders基础学习笔记_第8张图片

动态加载数据处理

selenium模块的基本使用

问题:selenium模块和爬虫之间具有怎样的关联?
    - 便捷的获取网站中动态加载的数据
    - 便捷实现模拟登录
什么是selenium模块?
    - 基于浏览器自动化的一个模块。

selenium使用流程:
    - 环境安装:pip install selenium
    - 下载一个浏览器的驱动程序(谷歌浏览器)
        - 下载路径:http://chromedriver.storage.googleapis.com/index.html
        - 驱动程序和浏览器的映射关系:http://blog.csdn.net/huilan_same/article/details/51896672
    - 实例化一个浏览器对象
    - 编写基于浏览器自动化的操作代码
        - 发起请求:get(url)
        - 标签定位:find系列的方法
        - 标签交互:send_keys('xxx')
        - 执行js程序:excute_script('jsCode')
        - 前进,后退:back(),forward()
        - 关闭浏览器:quit()

    - selenium处理iframe
        - 如果定位的标签存在于iframe标签之中,则必须使用switch_to.frame(id)
        - 动作链(拖动):from selenium.webdriver import ActionChains
            - 实例化一个动作链对象:action = ActionChains(bro)
            - click_and_hold(div):长按且点击操作
            - move_by_offset(x,y)
            - perform()让动作链立即执行
            - action.release()释放动作链对象

12306模拟登录
    - 超级鹰:http://www.chaojiying.com/about.html
        - 注册:普通用户
        - 登录:普通用户
            - 题分查询:充值
            - 创建一个软件(id)
            - 下载示例代码

    - 12306模拟登录编码流程:
        - 使用selenium打开登录页面
        - 对当前selenium打开的这张页面进行截图
        - 对当前图片局部区域(验证码图片)进行裁剪
            - 好处:将验证码图片和模拟登录进行一一对应。
        - 使用超级鹰识别验证码图片(坐标)
        - 使用动作链根据坐标实现点击操作
        - 录入用户名密码,点击登录按钮实现登录

selenium基础用法

爬取http://scxk.nmpa.gov.cn:81/xk/的企业名称

from selenium import webdriver
from lxml import etree
from time import sleep

# 实例化一个浏览器对象(传入浏览器的驱动)
browser = webdriver.Chrome(executable_path='./chromedriver.exe')
# 让浏览器发起一个指定url对应请求
browser.get('http://scxk.nmpa.gov.cn:81/xk/')
# page_source获取浏览器当前页面的页面源码数据
page_text = browser.page_source

# 解析企业名称
tree = etree.HTML(page_text)
li_list = tree.xpath('//ul[@id="gzlist"]/li')
for li in li_list:
    title = li.xpath('./dl/a/text()')[0]
    print(title)
sleep(3)
# 关闭浏览器
browser.quit()

selenium其他自动化操作

from selenium import webdriver
import time

browser = webdriver.Chrome(executable_path='./chromedriver.exe')
browser.get('https://www.taobao.com/')

# 定位标签input
search_input = browser.find_element_by_id('q')
# 标签交互,输入内容
search_input.send_keys('Python爬虫')
time.sleep(1)
#执行一组js程序,屏幕向下滑一屏
browser.execute_script('window.scrollTo(0,document.body.scrollHeight)')
time.sleep(2)

# 点击搜索按钮
search_btn = browser.find_element_by_css_selector('.tb-bg')
search_btn.click()


browser.get('https://www.baidu.com')
time.sleep(2)
#回退
browser.back()
time.sleep(2)
#前进
browser.forward()
time.sleep(3)
browser.quit()

动作链和iframe的处理

from selenium import webdriver
from time import sleep
# 导入动作链对应的类
from selenium.webdriver import ActionChains

browser = webdriver.Chrome('./chromedriver.exe')
browser.get('https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable')

# 如果定位的标签是存在于iframe标签之中的则必须通过如下操作在进行标签定位
browser.switch_to.frame('iframeResult')  #切换浏览器标签定位的作用域
son_div = browser.find_element_by_id('draggable')
# 动作链
action = ActionChains(browser)
# 点击长按指定的标签
action.click_and_hold(son_div)

for i in range(5):
    # perform()立即执行动作链操作
    # move_by_offset(x,y):x水平方向 y竖直方向
    action.move_by_offset(17,1).perform()
    sleep(0.5)
# 释放动作链
action.release()
sleep(1)
browser.quit()

模拟登录qq空间

from selenium import webdriver
from time import sleep

# 实例化一个浏览器对象(传入浏览器的驱动)
browser = webdriver.Chrome('./chromedriver.exe')
# 让浏览器发起一个指定url对应请求
browser.get('https://qzone.qq.com/')
sleep(5)

# 切换浏览器标签定位的作用域
browser.switch_to.frame('login_frame')
# 点击密码账号登录按钮
a_tag = browser.find_element_by_id('switcher_plogin')
sleep(2)
a_tag.click()

# 标签定位,找到账号,密码框
userNameTag = browser.find_element_by_id('u')
userPassTag = browser.find_element_by_id('p')
sleep(1)
# 输入账号,密码
userNameTag.send_keys('1066073302')
sleep(1)
userPassTag.send_keys('*********')
sleep(1)
# 点击登录按钮
login_btn = browser.find_element_by_id('login_button')
sleep(2)
login_btn.click()
sleep(6)
# 关闭浏览器
browser.quit()

谷歌无头浏览器+反检测

无头浏览器—>爬取不再显示浏览器,后台执行。反检测是一种反反爬策略,因为selenium模块过于火爆,所以有一些网站会检测访问是不是selenium动态加载数据,是的话就会被拒绝访问了,所以出现了反检测

from selenium import webdriver
from time import sleep
# 实现无可视化界面
from selenium.webdriver.chrome.options import Options
# 实现规避检测
from selenium.webdriver import ChromeOptions

# 实现无可视化界面的操作
chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-gpu')

# 实现规避检测
option = ChromeOptions()
option.add_experimental_option('excludeSwitches', ['enable-automation'])

# 实现让selenium规避被检测到的风险
browser = webdriver.Chrome(executable_path='./chromedriver.exe', chrome_options=chrome_options, options=option)

# 无可视化界面(无头浏览器)
browser.get('https://www.baidu.com')
print(browser.page_source)
sleep(2)
browser.quit()

你可能感兴趣的:(Python3.6.5)