两周从爬虫小白变大神,看完你就知道我不是标题党了【五万字教程,建议收藏】

目录

Python爬虫第一天

什么是爬虫

爬虫与Web后端服务之间的关系

Python爬虫技术的相关库

常见反爬虫的策略

爬虫库urllib【重要】

作业

爬虫第二天

回顾知识点

requests库【重点】

数据解析方式之xpath

绝对路径

相对路径

数据提取

位置条件

属性条件

在Python中应用

作业

爬虫第三天

回顾知识点

requests库

xpath解析

扩展封装ES-SDK

正则解析数据

扩展Linux文件权限

re面试中的问题

作业

爬虫第四天

回顾知识点

re正则

进程和线程

BS4数据解析

协程爬虫

协程的三种方式

协程第三方的框架

动态js渲染

Selenium

Splash

作业

爬虫最五天

回顾知识点

协程的爬虫

Seleinum库

Chrome-headless

Splash渲染

下载镜像

启动镜像

render.html接口

自动化测试

单元测试

集成测试

作业

爬虫第六天

回顾知识点

selenium框架

docker

日志模块进阶

日志格式

日志模块的核心

scrapy框架

scrapy架构组成

scrapy指令

Response类

Request类

作业

爬虫第七天

回顾知识点

scrapy框架

scrapy数据管道

指令方式存储

Pipeline

定量爬虫

基于信号方式

下载中间件

爬虫中间件

下载中间件 [重点]

作业

爬虫第八天

回顾知识点

数据处理

中间件

规则爬虫

规则爬虫【重】

LinkExtractor 类

核心的类

图片管道

使用ImagesPipeline

自定义ImagesPipeline

其它技术点

日志

post请求

Selenium中间件

作业

爬虫第九天

回顾知识点

Selenium下载中间件

分布式爬虫

什么是分布式

常见消息队列

scrapy-redis

爬虫程序部署

scrapyd

docker部署

作业

爬虫第十天

回顾爬虫技术

网络请求

数据解析

数据存储

爬虫框架

mongodb

docker部署

数据结构

常用操作

作业


 

Python爬虫第一天

学习内容:

  • 什么是爬虫(Spider)

  • 爬虫与Web后端服务之间的关系

  • Python爬虫技术的相关库

  • 常见反爬虫的策略

  • 爬虫库urllib【重要】

什么是爬虫

爬虫Spider的概念

爬虫用于爬取数据, 又称之为数据采集程序

爬取的数据来源于网络,网络中的数据可以是由Web服务器(Nginx/Apache)、数据库服务器(MySQL、Redis)、索引库(ElastichSearch)、大数据(Hbase/Hive)、视频/图片库(FTP)、云存储等(OSS)提供的。

爬取的数据是公开的、非盈利的。

Python爬虫

使用Python编写的爬虫脚本(程序)可以完成定时、定量、指定目标(Web站点)的数据爬取。主要使用多(单)线程/进程、网络请求库、数据解析、数据存储、任务调度等相关技术。

Python爬虫工程师,可以完成接口测试、功能性测试、性能测试和集成测试。

爬虫与Web后端服务之间的关系

爬虫使用网络请求库,相当于客户端请求, Web后端服务根据请求响应数据。

爬虫即向Web服务器发起HTTP请求,正确地接收响应数据,然后根据数据的类型(Content-Type)进行数据的解析及存储。

爬虫程序在发起请求前,需要伪造浏览器(User-Agent指定请求头),然后再向服务器发起请求, 响应200的成功率高很多。

Python爬虫技术的相关库

网络请求:

  • urllib

  • requests / urllib3

  • selenium(UI自动测试、动态js渲染)

  • appium(手机App 的爬虫或UI测试)

数据解析:

  • re正则

  • xpath

  • bs4

  • json

数据存储:

  • pymysql

  • mongodb

  • elasticsearch

多任务库:

  • 多线程 (threading)、线程队列 queue

  • 协程(asynio、 gevent/eventlet)

爬虫框架

  • scrapy

  • scrapy-redis 分布式(多机爬虫)

常见反爬虫的策略

  • UA(User-Agent)策略

  • 登录限制(Cookie)策略

  • 请求频次(IP代理)策略

  • 验证码(图片-云打码,文字或物件图片选择、滑块)策略

  • 动态js(Selenium/Splash/api接口)策略

爬虫库urllib【重要】

urllib.request模块

简单的请求

<span style="<span style="color:#333333"><span style="color:#770088">from</span> <span style="color:#000000">urllib</span>.<span style="color:#000000">request</span> <span style="color:#770088">import</span> <span style="color:#000000">urlopen</span>
​
<span style="color:#aa5500"># 发起网络请求</span>
<span style="color:#000000">resp</span> = <span style="color:#000000">urllopen</span>(<span style="color:#aa1111">'http://www.hao123.com'</span>)
<span style="color:#770088">assert</span> <span style="color:#000000">resp</span>.<span style="color:#000000">code</span> == <span style="color:#116644">200</span>
<span style="color:#3300aa">print</span>(<span style="color:#aa1111">'请求成功'</span>)
<span style="color:#aa5500"># 保存请求的网页</span>
<span style="color:#aa5500"># f 变量接收open()函数返回的对象的__enter__()返回结果</span>
<span style="color:#770088">with</span> <span style="color:#3300aa">open</span>(<span style="color:#aa1111">'a.html'</span>, <span style="color:#aa1111">'wb'</span>) <span style="color:#770088">as</span> <span style="color:#000000">f</span>:
     <span style="color:#000000">f</span>.<span style="color:#000000">write</span>(<span style="color:#000000">resp</span>.<span style="color:#000000">read</span>())</span></span>

urlopen(url, data=None)可以直接发起url的请求, 如果data不为空时,则默认是POST请求,反之为GET请求。

resp是http.client.HTTPResponse类对象。

带请求头的请求

<span style="<span style="color:#333333"><span style="color:#770088">from</span> <span style="color:#000000">urllib</span>.<span style="color:#000000">request</span> <span style="color:#770088">import</span> <span style="color:#000000">Request</span>
​
<span style="color:#770088">def</span> <span style="color:#0000ff">search_baidu</span>():
    <span style="color:#aa5500"># 网络资源的接口(URL)</span>
    <span style="color:#000000">url</span> = <span style="color:#aa1111">'https://www.baidu.com'</span>
​
    <span style="color:#aa5500"># 生成请求对象,封装请求的url和头header</span>
    <span style="color:#000000">request</span> = <span style="color:#000000">Request</span>(<span style="color:#000000">url</span>,
                      <span style="color:#000000">headers</span>={
                          <span style="color:#aa1111">'Cookie'</span>: <span style="color:#aa1111">'BIDUPSID=16CECBB89822E3A2F26ECB8FC695AFE0; PSTM=1572182457; BAIDUID=16CECBB89822E3A2C554637A8C5F6E91:FG=1; BD_UPN=123253; H_PS_PSSID=1435_21084_30211_30283; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598; H_PS_645EC=6f7aTIObS%2BijtMmWgFQxMF6H%2FhK%2FcpddiytCBDrefRYyFX%2B%2BTpyRMZInx3E'</span>,
                          <span style="color:#aa1111">'User-Agent'</span>: <span style="color:#aa1111">'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36'</span>
                      })
​
    <span style="color:#000000">response</span> = <span style="color:#000000">urlopen</span>(<span style="color:#000000">request</span>)  <span style="color:#aa5500"># 发起请求</span>
​
    <span style="color:#770088">assert</span> <span style="color:#000000">response</span>.<span style="color:#000000">code</span> == <span style="color:#116644">200</span>
    <span style="color:#3300aa">print</span>(<span style="color:#aa1111">'请求成功'</span>)
​
    <span style="color:#aa5500"># 读取响应的数据</span>
    <span style="color:#000000">bytes_</span> = <span style="color:#000000">response</span>.<span style="color:#000000">read</span>()
    
    <span style="color:#aa5500"># 将响应的数据写入文件中</span>
    <span style="color:#770088">with</span> <span style="color:#3300aa">open</span>(<span style="color:#aa1111">'index.html'</span>, <span style="color:#aa1111">'wb'</span>) <span style="color:#770088">as</span> <span style="color:#000000">file</span>:
        <span style="color:#000000">file</span>.<span style="color:#000000">write</span>(<span style="color:#000000">bytes_</span>)</span></span>

任务1:收集Http协议的报文头的哪些Key

urllib.parse模块

此模块有两个核心的函数:

  • quote() 仅对中文字符串进行url编码;

  • urlencode() 可以针对一个字典中所有的values进行编码,然后转成key=value&key=value的字符串。

<span style="<span style="color:#333333"><span style="color:#aa1111">"""</span>
<span style="color:#aa1111">复杂的GET请求,多页面请求下载</span>
<span style="color:#aa1111">"""</span>
<span style="color:#770088">from</span> <span style="color:#000000">urllib</span>.<span style="color:#000000">request</span> <span style="color:#770088">import</span> <span style="color:#000000">Request</span>, <span style="color:#000000">urlopen</span>
<span style="color:#770088">from</span> <span style="color:#000000">urllib</span>.<span style="color:#000000">parse</span> <span style="color:#770088">import</span> <span style="color:#000000">urlencode</span>
​
<span style="color:#770088">import</span> <span style="color:#000000">ssl</span>
​
<span style="color:#770088">import</span> <span style="color:#000000">time</span>
​
<span style="color:#000000">ssl</span>.<span style="color:#000000">_create_default_https_context</span> = <span style="color:#000000">ssl</span>.<span style="color:#000000">_create_unverified_context</span>
​
<span style="color:#000000">url</span> = <span style="color:#aa1111">'https://www.baidu.com/s?'</span>
​
<span style="color:#000000">headers</span> = {
    <span style="color:#aa1111">'User-Agent'</span>: <span style="color:#aa1111">'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36'</span>,
    <span style="color:#aa1111">'Cookie'</span>: <span style="color:#aa1111">'BIDUPSID=16CECBB89822E3A2F26ECB8FC695AFE0; PSTM=1572182457; BAIDUID=16CECBB89822E3A2C554637A8C5F6E91:FG=1; Hm_lvt_64ecd82404c51e03dc91cb9e8c025574=1573184257; REALTIME_TRANS_SWITCH=1; FANYI_WORD_SWITCH=1; HISTORY_SWITCH=1; SOUND_SPD_SWITCH=1; SOUND_PREFER_SWITCH=1; H_PS_PSSID=1435_21084_30211_30283; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598; delPer=0; PSINO=1; to_lang_often=%5B%7B%22value%22%3A%22en%22%2C%22text%22%3A%22%u82F1%u8BED%22%7D%2C%7B%22value%22%3A%22zh%22%2C%22text%22%3A%22%u4E2D%u6587%22%7D%5D; APPGUIDE_8_2_2=1; yjs_js_security_passport=0927713bf2c240ca607108086d07729426db4dbb_1577084843_js; __yjsv5_shitong=1.0_7_c3620451e4363f4aed30cbe954abf8942810_300_1577084847314_223.255.14.197_2d7151e0; from_lang_often=%5B%7B%22value%22%3A%22zh%22%2C%22text%22%3A%22%u4E2D%u6587%22%7D%2C%7B%22value%22%3A%22en%22%2C%22text%22%3A%22%u82F1%u8BED%22%7D%5D'</span>,
    <span style="color:#aa1111">'x-requested-with'</span>: <span style="color:#aa1111">'XMLHttpRequest'</span>
}
​
<span style="color:#000000">params</span> = {
    <span style="color:#aa1111">'wd'</span>: <span style="color:#aa1111">''</span>,
    <span style="color:#aa1111">'pn'</span>: <span style="color:#116644">0</span>  <span style="color:#aa5500"># 0, 10, 20, 30 ...  = (n-1)*10</span>
}
​
<span style="color:#770088">def</span> <span style="color:#0000ff">pages_get</span>(<span style="color:#000000">wd</span>):
    <span style="color:#000000">params</span>[<span style="color:#aa1111">'wd'</span>] = <span style="color:#000000">wd</span>
    <span style="color:#770088">for</span> <span style="color:#000000">page</span> <span style="color:#770088">in</span> <span style="color:#3300aa">range</span>(<span style="color:#116644">1</span>, <span style="color:#116644">101</span>):
        <span style="color:#000000">params</span>[<span style="color:#aa1111">'pn'</span>] = (<span style="color:#000000">page</span><span style="color:#981a1a">-</span><span style="color:#116644">1</span>)<span style="color:#981a1a">*</span><span style="color:#116644">10</span>
​
        <span style="color:#000000">page_url</span> = <span style="color:#000000">url</span><span style="color:#981a1a">+</span><span style="color:#000000">urlencode</span>(<span style="color:#000000">params</span>)
        <span style="color:#000000">resp</span> = <span style="color:#000000">urlopen</span>(<span style="color:#000000">Request</span>(<span style="color:#000000">page_url</span>,
                               <span style="color:#000000">headers</span>=<span style="color:#000000">headers</span>))
​
        <span style="color:#770088">assert</span> <span style="color:#000000">resp</span>.<span style="color:#000000">code</span> == <span style="color:#116644">200</span>
        <span style="color:#000000">file_name</span> = <span style="color:#aa1111">'baidu_pages/%s-%s.html'</span> <span style="color:#981a1a">%</span> (<span style="color:#000000">wd</span>, <span style="color:#000000">page</span>)
        <span style="color:#770088">with</span> <span style="color:#3300aa">open</span>(<span style="color:#000000">file_name</span>, <span style="color:#aa1111">'wb'</span>) <span style="color:#770088">as</span> <span style="color:#000000">f</span>:
            <span style="color:#000000">bytes_</span> = <span style="color:#000000">resp</span>.<span style="color:#000000">read</span>()
            <span style="color:#000000">f</span>.<span style="color:#000000">write</span>(<span style="color:#000000">bytes_</span>)
            <span style="color:#3300aa">print</span>(<span style="color:#aa1111">f'{file_name} 写入成功!'</span>)
            <span style="color:#000000">time</span>.<span style="color:#000000">sleep</span>(<span style="color:#116644">0.5</span>)
​
    <span style="color:#3300aa">print</span>(<span style="color:#aa1111">'下载 %s 100页成功!'</span> <span style="color:#981a1a">%</span> <span style="color:#000000">wd</span>)
​
​
<span style="color:#770088">if</span> <span style="color:#000000">__name__</span> == <span style="color:#aa1111">'__main__'</span>:
    <span style="color:#000000">pages_get</span>(<span style="color:#aa1111">'Python3.6'</span>)</span></span>

 

HTTP处理器

urllib的请求处理器,主要用于urllib.request.build_opener()函数参数,表示构造一个由不同处理组成的伪浏览器。

HTTPHandler

处理Http协议的请求处理。

5HTTPCookieProcessor

处理Cookie的处理器,创建类实例时,需要提供http.cookiejar.CookieJar类的实例对象。

ProxyHandler

 

作业

  • 写出Python上下文的两个核心函数

    <span style="<span style="color:#000000">__enter__</span>(<span style="color:#0055aa">self</span>)
    <span style="color:#000000">__exit__</span>(<span style="color:#0055aa">self</span>, <span style="color:#000000">except_type</span>, <span style="color:#000000">except_value</span>, <span style="color:#000000">except_tb</span>)</span>

     

  • 写出正则中的(), [] , {} 三个符号的作用

    <span style="background-color:#f8f8f8">( ) 用于分组的符号
    [ ] 指定匹配字符的范围,如 [a-c_B-F]
    { } 指定匹配的长度(量词表示)</span>

     

  • 写出pymysql.Connect()连接数据库的核心参数

    <span style="<span style="color:#000000">Connect</span>(<span style="color:#000000">host</span>,
                    <span style="color:#000000">port</span>=<span style="color:#116644">3306</span>,
            <span style="color:#000000">user</span>,
            <span style="color:#000000">password</span>,
            <span style="color:#000000">db</span>,
            <span style="color:#000000">charset</span>)</span>

     

  • 豆瓣动作电影排行榜

  • 肯德基店铺位置

 

爬虫第二天

回顾知识点

核心的网络请求库 -> urllib库

  • urllib.request 模块

    • urlopen(url | request: Request, data=None) data是bytes类型

    • urlretrieve(url, filename) 下载url的资源到指定的文件

    • build_opener(*handlder) 构造浏览器对象

      • opener.open(url|request, data=None) 发起请求

    • Request 构造请求的类

    data={
      'wd': ''
    }
    # urlencode(data)  => 'wd=%f5%e6%e6%f5%e6%e6'
    request = Request(url, data=urlencode(data).encode())
    • HTTPHandler HTTP协议请求处理器

    • ProxyHandler(proxies={'http': 'http://proxy_ip:port'}) 代理处理

    • HTTPCookieProcessor(CookieJar())

      • http.cookiejar.CookieJar 类

  • urllib.parse模块

    • quote(txt) 将中文字符串转成url编码

    • urlencode(query: dict) 将参数的字典转成url编码,结果是key=value&key=value形式,即以 application/x-www-form-urlencoded作为url编码类型。

requests库【重点】

requests库也是一个网络请求库, 基于urllib和urllib3封装的便捷使用的网络请求库。

安装环境

pip install requests -i https://mirrors.aliyun.com/pypi/simple

核心的函数

  • requests.request() 所有请求方法的基本方法

    以下是request()方法的参数说明

    • method: str 指定请求方法, GET, POST, PUT, DELETE

    • url: str 请求的资源接口(API),在RESTful规范中即是URI(统一资源标签识符)

    • params: dict , 用于GET请求的查询参数(Query String params);

    • data: dict , 用于POST/PUT/DELETE 请求的表单参数(Form Data)

    • json: dict 用于上传json数据的参数, 封装到body(请求体)中。请求头的Content-Type默认设置为application/json

    • files: dict, 结构 {'name': file-like-object | tuple}, 如果是tuple, 则有三种情况:

      • ('filename', file-like-object)

      • ('filename', file-like-object, content_type)

      • ('filename', file-like-object, content_type, custom-headers)

      指定files用于上传文件, 一般使用post请求,默认请求头的Content-Typemultipart/form-data类型。

    • headers/cookies : dict

    • proxies: dict , 设置代理

    • auth: tuple , 用于授权的用户名和口令, 形式('username', 'pwd')

  • requests.get() 发起GET请求, 查询数据

    可用参数:

    • url

    • params

    • json

    • headers/cookies/auth

  • requests.post() 发起POST请求, 上传/添加数据

    可用参数:

    • url

    • data/files

    • json

    • headers/cookies/auth

  • requests.put() 发起PUT请求, 修改或更新数据

  • requests.patch() HTTP幂等性的问题,可能会出现重复处理, 不建议使用。用于更新数据

  • requests.delete() 发起DELETE请求,删除数据

requests.Respose

以上的请求方法返回的对象类型是Response, 对象常用的属性如下:

  • status_code 响应状态码

  • url 请求的url

  • headers : dict 响应的头, 相对于urllib的响应对象的getheaders(),但不包含cookie。

  • cookies: 可迭代的对象,元素是Cookie类对象(name, value, path)

  • text : 响应的文本信息

  • content: 响应的字节数据

  • encoding: 响应数据的编码字符集, 如utf-8, gbk, gb2312

  • json(): 如果响应数据类型为application/json,则将响应的数据进行反序化成python的list或dict对象。

    • 扩展-javascript的序列化和反序列化

      • JSON.stringify(obj) 序列化

      • JSON.parse(text) 反序列化

 

数据解析方式之xpath

xpath属于xml/html解析数据的一种方式, 基于元素(Element)的树形结构(Node > Element)。选择某一元素时,根据元素的路径选择,如 /html/head/title获取<title>标签。

绝对路径

从根标签开始,按tree结构依次向下查询。

如 /html/body/table/tbody/tr。

相对路径

相对路径可以有以下写法

  • 相对于整个文档

    //img

    查找出文档中所有的<img>标签元素

  • 相对于当前节点

    //table

    假如当前节点是<table>, 查找它的<img>的路径的写法

    .//img

数据提取

  • 提取文本

    //title/text()
  • 提取属性

    //img/@href

位置条件

获取网页中的数据类型与字符集, 获取第一个<meta>标签

//meta[1]//@content

获取最后一个<meta>标签

//meta[last()]//@content

获取倒数第二个<meta>标签

//meta[position()-2]//@content

获取前三个<meta>标签

//meta[position()<3]//@content

属性条件

查找 class为circle-img<img>标签

//img[@class="circle-img"]

 

在Python中应用

安装包 pip install lxml

 

作业

  • 写出urllib库的请求处理器有哪些类(尽量写全路径)

    • urllib.request.HTTPHandler

    • urllib.request.HTTPCookieProcessor

    • urllib.request.ProxyHandler

  • 写出json.loads()和pickle.loads()返回的数据类型

    • json.loads() 返回list或dict, 加载的是字符串

    • pickle.loads() 返回是python中的对象, 加载的是字节数组 bytes

  • 写出pymysql的cursor.execute()方法中的参数及作用

    • 有两个参数, 一个是sql, 一个是args

    • args可以是tuple,对应sql字符串的 %s

    • args也可以是dict, 对应sql字符串的%(xxx)s, xxx是dict中的key

  1. 买家秀的模特的所有图片, 图片的名称是姓名-序号, 如'Disen-1.jpg', 'Disen-2.jpg'

  2. 中国图书网

    爬虫所有的小说的基本信息(名称、作者、出版社、原价、折扣价、活动标签、 简介)

扩展

  • 在Ubuntu 下安装docker

  • 基于docker部署ElasticSearch搜索引擎库。

  • 基于requests实现索引库及文档的添加和查询。

 

爬虫第三天

回顾知识点

requests库

  • requests.request(method, url, **kwargs)

    常用的参数

    • params/data/json 上传数据

    • files 上传文件

    • headers/cookies

    • proxies 代理服务器

    • auth 授权

  • requests.get(url, params, **kwargs)

  • requtests.post(url, data, json, **kwargs)

  • requests.put(url, data, json, **kwargs)

  • requests.delete(url, **kwargs)

  • requests.session() - > session对象, 可以调用 s.get()/post()/put()/delete()等方法,多次请求的会话(连接Session)是同一个

所有的请求返回的对象是requests.Response类的实例, 实例的属性:

  • status_code

  • headers

  • encoding

  • text/content

  • cookies

  • json() 反序列化json文本字符串为python的list或dict的对象

xpath解析

  • 路径写法

    • / 依次查找

    • // 间接查找

    • ./ 从当前元素下查找

    • .// 从当前元素的间接子节点查找

  • 位置条件

    • //li[1] 整个文档中的第一个<li>标签

    • //li[last()] 最后一个

    • //li[position() < 3] 前2个

    • //li[position() - 2] 倒数第2个

  • 属性条件

    • //li[@id="xxxx"]

    • //li[@class=""] @class 属性名

    • //li[@class="" and @name=""] 多个属性的且的关系

  • 同时提取两个元素

    • //title/text() | //img/@src

  • 模糊条件

    • //div[contains(@class, "page")] 查找class属性包含page的所有div标签

    • //div[starts-with(@class, "box")] 第一个class的属性值为box的div标签

    • //div[ends-with(@class, "clearfix")]最一个class的属性值为clearfix的div标签

扩展封装ES-SDK

"""
基于requests库封装操作ElasticSearch搜索引擎的函数 (SDK)
"""
from urllib.parse import quote
​
import requests
​
INDEX_HOST = '119.3.170.97'
INDEX_PORT = 80
​
​
class ESIndex():
    """ES的索引库的类"""
​
    def __init__(self, index_name, doc_type):
        self.index_name = index_name
        self.doc_type = doc_type
​
    def create(self):  # 创建索引库
        url = f'http://{INDEX_HOST}:{INDEX_PORT}/{self.index_name}'
        json_data = {
            "settings": {
                "number_of_shards": 5,
                "number_of_replicas": 1
            }
        }
        resp = requests.put(url, json=json_data)
        if resp.status_code == 200:
            print('创建索引成功')
            print(resp.json())
​
    def delete(self):  # 删除索引库
        resp = requests.delete(f'http://{INDEX_HOST}:{INDEX_PORT}/{self.index_name}')
        if resp.status_code == 200:
            print('delete index ok')
​
    def add_doc(self, item: dict):
        # 向库中增加文档
        doc_id = item.pop('id', None)
        url = f'http://{INDEX_HOST}:{INDEX_PORT}/{self.index_name}/{self.doc_type}/'
        if doc_id:
            url += str(doc_id)
​
        resp = requests.post(url, json=item)
        if resp.status_code == 200:
            print(f'{url} 文档增加成功!')
​
    def remove_doc(self, doc_id):
        # 删除文档
        url = f'http://{INDEX_HOST}:{INDEX_PORT}/{self.index_name}/{self.doc_type}/{doc_id}'
        resp = requests.delete(url)
        if resp.status_code == 200:
            print(f'delete {url} ok')
​
    def update_doc(self, item: dict):
        # 更新文档
        doc_id = item.pop('id')
        url = f'http://{INDEX_HOST}:{INDEX_PORT}/{self.index_name}/{self.doc_type}/{doc_id}'
        resp = requests.put(url, json=item)
        assert resp.status_code == 200
        print(f'{url} update ok')
​
    def query(self, wd=None):
        # 查询
        q = quote(wd) if wd else ''
        url = f'http://{INDEX_HOST}:{INDEX_PORT}/{self.index_name}/_search?size=100'
        if q:
            url += f'&q={q}'
        resp = requests.get(url)
        datas = []
        if resp.status_code == 200:
            ret = resp.json()
            hits = ret['hits']['hits']
            if hits:
                for item in hits:
                    data = item['_source']
                    data['id'] = item['_id']
​
                    datas.append(data)
​
        return datas
​
​
if __name__ == '__main__':
    index = ESIndex('gushiwen', 'tuijian')
    # index.create()
    # index.add_doc({
    #     'id': 1,
    #     'name': 'disen',
    #     'price': 19.5
    # })
    #
    # index.add_doc({
    #     'id': 2,
    #     'name': 'jack',
    #     'price': 10.5
    # })
​
    print(index.query())

正则解析数据

扩展Linux文件权限

100  ->4 -> r
​
010 -> 2-> w
​
001 -> 1 -> x
​
​
​
100 | 010 = 110  # 增加权限
​
110 & 100 == 100  # 验证100权限
​
110 ^ 100 = 010   # 删除100权限 

re面试中的问题

  • compile() /match()/search() 三者之间的区别

  • search()/findall()区别

  • 贪婪模式和非贪婪模式

 

解析站长之家

"""
基于正则re模块解析数据
"""
import re
import os
​
import requests
​
from utils.header import get_ua
​
base_url = 'http://sc.chinaz.com/tupian/'
url = f'{base_url}shuaigetupian.html'
​
headers = {
    'User-Agent': get_ua()
}
​
if os.path.exists('mn.html'):
    with open('mn.html', encoding='utf-8') as f:
        html = f.read()
else:
    resp = requests.get(url, headers=headers)
    print(resp.encoding)  # IOS-8859-1
    resp.encoding = 'utf-8'  # 可以修改响应的状态码
    assert resp.status_code == 200
    html = resp.text
    with open('mn.html', 'w', encoding=resp.encoding) as f:
        f.write(html)
​
# print(html)
#  [\u4e00-\u9fa5]
compile = re.compile(r'<img src2="(.*?)" alt="(.*?)">')
compile2 = re.compile(r'<img alt="(.*?)" src="(.*?)">')
​
imgs = compile.findall(html)  # 返回list
if len(imgs) == 0:
    imgs = compile2.findall(html)
​
print(len(imgs), imgs, sep='\n')
​
# 下一页
next_url = re.findall(r'<b>20</b></a><a href="(.*?)" class="nextpage"',html, re.S)
​
print(base_url+next_url[0])

 

作业

  • 写出requests.request()方法常用的参数及参数类型

    • method: str 请求方法, 可以指定 get, post, put, delete, options

    • url : str 请求路径或api接口

    • params/data/json : dict 上传的请求参数及json或form的data数据

    • headers/cookie: dict 请求头或Cookie信息

    • files: dict 上传的文件信息

  • 写出正则的贪婪模式有哪些

    • .* 0或多个任意字符

    • .+ 1或多个任意字符

    • .?

    • .{n, } 至少n个以上的任意字符

    • .{n, m} 至少n个以上的任意字符

  • 写出str对象的常用方法(10+)

    • join()

    • split()

    • strip()

    • replace()

    • upper()

    • lower()

    • title()

    • index()/rindex()

    • find()/rfind()

    • insert()

    • just()/ljust()/rjust()

    • capitalize() # 每个单词的首字母大写

      要求: 自动生成订单号
      订单号的格式: 20191226000001
      当天的单号按自增,第二天的序号是从1开始。
    • count()

    • encode()

    • startswith()/endswith()

    • format()

 

  1. 基于Flask实现文件上传服务器, 通过requests测试文件上传接口。

  2. 优化美女网爬虫,将数据存到es搜索引擎中

  3. 完成站长之家的多任务爬虫的数据存储(ES引擎/csv)

 

爬虫第四天

回顾知识点

re正则

  • 字符的表示

    • . 任意一个字符, 除了换行

    • [a-f] 范围内的任意一个字符

    • \w 字母、数字和下划线组成的任意的字符

    • \W

    • \d

    • \D

    • \s

    • \S

  • 量词(数量)表示

    • * 0或多个

    • + 1或多个

    • ? 0 或 1 个

    • {n} n 个

    • {n,} 至少n个

    • {n, m} n~m个

  • 分组表示

    • ( ) 普通的分组表示, 多个正则分组时, search().groups() 返回是元组

    • (?P<name> 字符+数量)带有名称的分组, 多个正则分组时,search().groupdict()返回是字典, 字典的key即是分组名。

      import re
      ​
      text = '123abc90ccc'
      re.search(r'(?P<n1>\d+?)[a-z]+?(?P<n2>\d+)', text).groupdict()
  • Python中的正则模块

    • re.compile() 一次生成正则对象,可以多次匹配查询

    • re.match(正则对象, 字符串)

    • re.search()

    • re.findall()

    • re.sub()

      re.sub('\d+', '120', text) # 将text中的所有数字替换成120

      分享面试题:

      给定列表,每一个元组中包含字母和数字, 要求字母和数字分开排序
      如:
      ['abc12', 'abc9', 'abc10', 'ac8', 'ac12']
      排序之后结果是:
      ['abc9', 'abc10', 'abc12', 'ac8', 'ac12']
      def format_number(item):
          replace_number = re.findall(r'\d+',item)[0].rjust(2, '0')
            return re.sub(r'\d+',replace_number,item)
      ​
      arr = ['abc12', 'abc9', 'abc10', 'ac8', 'ac12']
      sorted(arr, key=format_number)
    • re.split()

进程和线程

  • multiprocessing模块(进程)

    • Process 进程类

    • Queue 进程间通信的队列

      • put(item, timeout)

      • item = get(timeout)

  • threading 模块(线程)

    • Thread 线程类

    • 线程间通信(访问对象)

      • queue.Queue 线程队列

      • 回调函数(主线程声明, 子线程调用函数)

BS4数据解析

  • 安装包

    pip install bs4

  • from bs4 import BeautifulSoup

  • 生成bs4根节点对象

    root = BeautifulSoup(html, 'lxml')

  • 查找节点(bs4.element.Tag)

    • root.find('标签名', class_="", id_="") 返回单节点Tag对象

    • root.find_all('标签名', class_="", id_="", limit=3) 返回limit指定数量的Tag对象的列表

    • root.select('样式选择器')

      • #id

      • .class

      • 标签名

      • [属性]

      • div ul 间接子节点, 或 div > ul直接子节点

  • 节点的属性

    • 获取文本数据

      • div.text/div.string/div.get_text()

    • 获取属性

      • div.get('属性名')

      • div['属性名']

      • div.attrs['属性名']

      • div.attrs.get('属性名')

    • 获取子节点

      • contents 获取所有文本子节点

      • descendants 获取所有子节点对象

协程爬虫

协程是线程的替代品, 区别在于线程由CPU调度, 协程由用户(程序)自己的调度的。协程需要事件监听模型(事件循环器),它采用IO多路复用原理,在多个协程之间进行调度。

协程的三种方式

  • 基于生成器 generator (过渡)

    • yield

    • send()

  • Python3 之后引入了 asyncio模块

    • @asyncio.coroutine 协程装饰器, 可以在函数上使用此装饰器,使得函数变成协程对象

    • 在协程函数中,可以使用yield from 阻塞当前的协程,将执行的权限移交给 yield from 之后的协程对象。

    • asyncio.get_event_loop() 获取事件循环模型对象, 等待所有的协程对象完成之后结束。

  • Python3.5之后,引入两个关键字

    • async 替代 @asyncio.coroutine

    • await 替代 yield from

协程第三方的框架

  • gevent

  • eventlet

  • Tornado/Twisted

动态js渲染

Selenium

Selenium是驱动浏览器(chrome, firefox, IE)进行浏览器相关操作(打开url, 点击网页中按钮功连接、输入文本)。

在Python程序中使用时,需要selenium的库和相关浏览的驱动程序(Window, Linux, Mac)。

 

Splash

Splash 是Web服务, 基于WebKit技术框架,可以动态加载网页。

 

作业

  • 写出生成dict对象的方式有哪些

    • { }

    • dict([(key, value), ..])

    • json.loads('json格式的字符串')

    • OrderDict

    • dict(zip([ ], [ ]))

    • dict.fromkeys([], value)

    • dict(key=value, key=value)

  • 写出bs4查找的节点对象的类是什么,它有哪些属性及方法

    • bs4.element.Tag/bs4.Tag 节点对象的类

    • Tag的方法

      • find()

      • find_all()

      • select()

      • get_text()

    • Tag的属性

      • string 标签的文本

      • text 标签的文本

      • contents 所有文本的子节点

      • descendants 所有的子节点对象

      • attrs 属性字典

  • 写出创建线程Thread类的实例时的参数有哪些

    提示: Thread(参数列表)

    • name 线程名

    • target 线程执行的目标函数

    • args 函数中位置传参, tuple

    • kwargs 指定函数中关键参数传值 , dict

  • 使用docker搭建Splash服务

  • 股票信息提取

  • 腾讯公司招聘需求抓取

 

爬虫最五天

回顾知识点

协程的爬虫

  • 协程和线程区别

    线程是CPU调度的,多线程是共享同一进程中的内存的(线程本地变量 Local、同步锁Lock、条件变量)。
    线程是threading模块
    协程是在线程(主)中声明及调度的。协程由用户(程序)调度的,是基于事件模型(IO多路复用模型-select/poll/epoll)。
    协程是asyncio模块(Python 3.4+)
  • 协程的知识点

    • @asyncio.coroutine 将函数升级为协程对象(闭包函数)

    • yield from 将执行的权限移交给其它协程对象

    • loop = asyncio.get_event_loop() 获取事件模型

    • loop.run_until_complete(协程对象) 事件模型启动协程,直到协程执行完成后,释放事件模型对象。

      • 如果是多个协程对象时, 需要使用asyncio.wait() 将多个协程对象以元组方式传入到wait()方法中。

    • Python 3.5+增加两个关键字

      • async 替代@asyncio.coroutine

      • await 替代 yield from

      注意: async 和 await 必须同时使用

Seleinum库

  • 安装python库

    pip install selenium
  • 下载浏览器的驱动

    • chrome

    • firefox

  • 在Python中使用

    • selenium.webdriver.common.by.By

      • By.CLASS_NAME

      • By.CSS_SELECTOR

      • By.ID

      • By.NAME

      • By.TAG_NAME

      • By.XPATH

      • By.LINK_TEXT

    • selenium.webdriver.Chrome

      • 在实例化Chrome()对象中, 需要指定driver.exe浏览驱动程序的位置。如果位置在环境变量的Path添加了,则不需要指定位置参数。

      • chrome.get(url) 打开url

      • chrome.find_element(by, value) 根据by 查找value的一个元素。

      • chrome.find_elements(by, value) 查找多个元素

      • chrome.window_handlers: list 可以获取窗口标签页

      • chrome.execute_script(js) 当前窗口中执行js脚本

      • chrome.swich_to.window/frame() 切换窗口

      • chrome.close()

      • chrome.page_source 渲染之后的html网页源码

    • WebElement 是查找元素的对象类型

      • click() 点击

      • send_keys() 输入内容

    • 等待WebElement元素出现

      • selenium.webdriver.support 模块

        • ui

          • WebDriverWait(driver, timeout)

            • until(expected_conditions, err_msg)

        • expected_conditions

          • visibility_of_all_elements_located((By, value))

Chrome-headless

 options = Options()
    options.add_argument('--headless')
    options.add_argument('--disable-gpu')
    # options.binary_location=r'/Users/apple/PycharmProjects/xpy905_spider/day04/chromedriver'
​
    chrome = Chrome(options=options)

headless 无窗口

Splash渲染

下载镜像

通过docker下载splash镜像

官方服务器

sudo docker pull scrapinghub/splash

私有的docker仓库下载

sudo docker pull 10.36.173.95:5000/splash

启动镜像

sudo docker run -itd --name splash-s -p 8050:8050 10.36.173.95:5000/splash

-p 指定宿主机和容器的端口映射关系,

格式: [宿主机端口]:[容器的端口]

-d 后台启动, -t 可以打开容器的终端, -i 可进入容器

进入容器

sudo docker exec -it splash-s bash

未进入容器时,也可以通过exec命令执行容器中的命令

sudo docker exec splash-s ls -l

ls -l 命令会列出splash-s容器当前的目录下的所有文件。

render.html接口

渲染动态js的接口: http://ip:port/render.html

接口的参数:

  • url 目标的网址

  • wait 等待渲染或加载的时间

  • proxy

  • headers

  • timeout

如,渲染jd.com网页

http://10.36.173.186:8050/render.html?url=https://jd.com

自动化测试

单元测试

Pythonu单元测试模块-unittest

from unittest import TestCase

class TestIndex(TestCase):
    def setUp(self):
        print('--测试前的资源准备工作---')

    def test_a_add_index(self):
        print('--添加索引--')
        data['index_name'] = 'person_sos'

    def test_b_query_index(self):
        print('--查询索引--')

    def test_c_delete_index(self):
        print('--删除索引--')

    def tearDown(self):
        print('--测试后的资源回收工作---')

 

集成测试

单元测试套件 unitest.TestSuit

"""
使用单元测试,测试ES搜索引擎的RESTful接口
- requests
- unittest
"""

from unittest import TestCase, TestSuite, TextTestRunner

data = {

}

class TestDoc(TestCase):
    def test_a2_add_doc(self):
        print(f'-{data["index_name"]}-增加doc文档--')

    def test_a3_query_doc(self):
        print(f'-{data["index_name"]}-查询doc文档--')


if __name__ == '__main__':
    # 必须以普通的Python脚本运行
    
    suite = TestSuite()
    suite.addTest(TestIndex.test_a_add_index)
    suite.addTest(TestDoc.test_a2_add_doc)
    suite.addTest(TestDoc.test_a3_query_doc)

    TextTestRunner().run(suite)

作业

  • 写出Python协程的模块及核心函数

  • 写出Selenium的查找元素的方式

  • 写出Python获取命令行参数的方式

 

爬虫第六天

回顾知识点

爬虫的认知

- 数据请求(网络请求库)
- 数据解析(re/xpath/bs4)
- 数据存储(csv/pymysql/json??)
- 反反爬的策略
    - ip代理 
    - ua池
    - cookie池: 收集手动登录之后的响应的Cookie信息
    - 请求间隔(2~5秒)
    - 验证码处理(打码平台、机器学习???)

网络请求库

- urllib
    - request
        - urlopen()
        - urlretrieve(fullurl, filename)
        - Request(url, data=None, headers)
        - build_opener(*handlers)
        - HTTPHandler
        - HTTPCookieProcessor(http.cookiejar.CookieJar())
        - ProxyHandler(proxies={})
    - parse
        - quote()
        - urlencode()
- http.client.HTTPResponse
    - code
    - getheaders()
    - getheader(name, default)
    - read()  读取的响应字节数据
    
        
- requests (第三方)
    - request(method, url, params, data, json, files, headers, cookies, proxies, auth)
    - get(url, params, **kwargs)
    - post(url, data, json, **kwargs)
    - put(url, data, json, **kwargs)
    - delete(url, **kwargs)
    - Response
        - status_code
        - encoding
        - headers
        - content 字节数据
        - text 文本数据
        - json()  json文本反序列化为Python的dict/list的对象

数据解析

- re
- xpath  (pip install lxml)
    - from lxml import etree
      root = etree.HTML(html)
      root.xpath('')  # list[''] / list[<Element>, ]
  - 返回文本列表的xpath表示
    - @href/@src 标签属性
    - text() 标签文本
  - 返回Element元素列表
    - //title
    - //ul/li[1]
    
- bs4 (pip install bs4)
    - from bs4 import BeautifulSoup
      root = BeautifulSoup(html, 'lxml')  # bs4.element.Tag  
    - 查询元素标签的方法
        - find('标签名', class_, id_) 查找第一个
        - find_all('标签名', class_, id_, limit=N) 查找前N个
        - select('css选择器')
            - #id
            - .classname
            - 标签名
            - 后代标签
            - 兄弟标签 (查找多个标签)
            - 属性标签
            - 伪类
            
    - Tag属性
        - string/text
        - get_text()
        - attrs: dict 标签中所有属性的字典
        - contents 子标签的文本列表
        - descendants 子标签的Tag列表

多任务爬虫

  • 多线程

    • threading

      • Thread

    • queue.Queue 线程队列

  • 多进程

    • multiprocessing

      • Process

      • Queue 进程队列

  • 协程

    • asyncio

      • coroutine 协程装饰器

      • get_event_loop()

      • wait()

      • sleep()

    • yield from

    • async / await

selenium框架

以driver程序驱动浏览器,对目标(网站或网页)进行操作(请求网页、提取数据、截图、切换或关闭页签-window)。
- chrome.get() 打开目标(发起请求)
- chrome.quit() 退出浏览器
- chrome.close() 关闭当前的窗口
- chrome.find_element(By, value)
    - selenium.webdriver.common.by.By
        - ID
        - CLASS_NAME
        - NAME
        - XPATH
        - CSS_SELECTOR
        _ LINK_TEXT
    - WebElement 查到的标签对象
        - get_attribute('属性名', default)
        - text 标签文本
        - click()
        - send_keys()
        - rect 当前元素的位置(left, top, width, height)
        
- chrome.find_elements(By, value)
- execute_script()
- save_screenshot(filename)  截图
- 等待某一个标签元素出现
    - selenium.webdriver.support
        - ui
            - WebDriverWait
        - expected_conditions
            - visibility_of_all_elements_located((By, value))
        
    ui.WebDriverWait(dirver, timeout).until(
            expected_conditions, 
            error_msg
    )

docker

容器技术,将远程的docker仓库中的镜像下拉到本地, 再将镜像运行成为一个容器(进程)。
- 镜像操作
    - 基本信息 
        - 名称
        - 版本
        - ID
        - 描述
    - docker images 查看所有镜像
    - docker rmi 名称:版本号 / ID 删除镜像
    - docker run 名称:版本号 / ID 启动镜像
        - -dit 后台启动镜像,启动后可进入容器并打开新的terminal(终端)
        - -p 宿主机端口: 容器端口
- 容器操作
    - docker ps 查看正运行的容器
        - -a 查看所有的容器
        - -l 查看最后一个启动的容器
        
    - docker logs 容器名或ID  查看容器运行的日志
    - docker exec 容器名或ID Linux命令 在容器中执行Linux命令
        - docker exec -it 容器名或ID bash 进入容器
    - docker stop 容器名或ID
    - docker start 容器名或ID
    - docker restart 容器名或ID
    - docker rm -f 容器名或ID 删除容器, -f强制删除正运行的容器

日志模块进阶

日志格式

格式说明
%(name)s 记录器的名称, 默认为root
%(levelno)s 数字形式的日志记录级别
%(levelname)s 日志记录级别的文本名称
%(filename)s 执行日志记录调用的源文件的文件名称
%(pathname)s 执行日志记录调用的源文件的路径名称
%(funcName)s 执行日志记录调用的函数名称
%(module)s 执行日志记录调用的模块名称
%(lineno)s 执行日志记录调用的行号
%(created)s 执行日志记录的时间
%(asctime)s 日期和时间
%(msecs)s 毫秒部分
%(thread)d 线程ID
%(threadName)s 线程名称
%(process)d 进程ID
%(message)s 记录的消息
- Python脚本中执行当前操作系统的命令的方法
  - os.chdir() 切换当前目录
    - os.system() 无返回结果 (打开一个子进程执行 命令)
    - os.popen()  可读取返回结果

日志模块的核心

  • 四大核心

    • 日志记录器 Logger

    • 日志处理器Handler

    • 日志的过滤器Filter

    • 日志的格式化Formatter

scrapy框架

scrapy架构组成

  • 五个核心

    • engine 引擎, 协调其它四个组件之间的联系,即与其它四个组件进行通信,也是scrapy框架的核心。

    • spider 爬虫类, 爬虫程序的编写代码所在, 也是发起请求的开始的位置。spider发起的请求,经过engine转入到scheduler中。

    • scheduler 调度器, 调度所有的请求(优先级高,则会先执行)。当执行某一个请求时,由engine转入到downloader中。

    • donwloader 下载器, 实现请求任务的执行,从网络上请求数据,将请求到的数据封装成响应对象,并将响应的对象返回给engine。engine将数据响应的数据对象(以回调接口方式)回传给它的爬虫类对象进行解析。

    • itempipeline 数据管道, 当spider解析完成后,将数据经engine转入到此(数据管道)。再根据数据类型,进行数据处理(图片、文本)

  • 二个中间件

    • 爬虫中间件, 介于Spider和Engine之间的,可以拦截Spider的发起的请求及数据。

    • 下载中间件,介于Engine和Downloader之间的,可以拦截下载和响应。当然在下载处理之前,可以设置代理 、请求头、Cookie等操作(反反爬设置),还可以基于Splash或Selenium实现特定的操作。

scrapy指令

  • 创建项目命令

    • scrapy startproject 项目名称

  • 创建爬虫命令

    • scrapy genspider 爬虫名 域名

  • 启动爬虫命令

    • scrapy crawl 爬虫名

  • 调试爬虫命令

    • scrapy shell url

    • scrapy shell

      • fetch(url)

Response类

  • 属性相关【重点】

    • body 响应的字节数据

    • text 响应的编码之后文本数据

    • headers 响应头信息, 是字节数据

    • encoding 响应数据的编码字符集

    • status 响应的状态码

    • url 请求的url

    • request 请求对象

    • meta 元数据,用于request和callback回调函数之间传值

  • 解析相关【重点】

    • selector()

    • css() 样式选择器 , 返回Selector选择器的可迭代(列表)对象

      • scrapy.selector.SelectorList 选择器列表

        • x()/xpath()

      • scrapy.selector.Selector 选择器

      • 样式选择器提取属性或文本

        • ::text 提取文本

        • ::attr("属性名") 提取属性

    • xpath() xpath路径

      xpath路径,同lxml的xpath()写法

    • 选择器常用方法

      • css()/xpath()

      • extract() 提取选择中所有内容,返回是list

      • extract_first()/get() 提取每个选择器中的内容, 返回是文本

Request类

  • scrapy.http.Request

    请求对象的属性

    • url

    • callback 解释数据的回调函数对象

    • headers 请求头

    • priority 请求的优先级, 值越高,优先级越高(优先下载)

作业

  • 写出selenium向下和向右滚动的脚本

    document.documentElement.scrollTop 向下

    document.documentElement.scrollLeft 向右

  • 写出restful接口设计规范(四个)

    • 每个资源都有唯一标识 URI

    • 每个资源具有四个动作, GET|POST|PUT|DELETE

    • 每次请求都是无状态

    • 接口交互的数据是json或xml

  • 写出常见的反爬虫和反反爬虫

    • 访问次数 - IP代理

    • Cookie验证- Cookie池

    • UA验证 - UA池

    • 验证码 - 打码平台

    • 动态js渲染 - Selenium/Splash

  • 爬取 陕西省政府采购网 陕西省采购网

  • 基于Flask实现日志上报服务器(日志微服务)

    • logging.handlers.HTTPHandler

 

爬虫第七天

回顾知识点

日志模块

import logging
from logging import StreamHandler, FileHandler
  • 四个核心部分

    • 日志记录器logger: 记录日志信息

    • 日志处理器 handler: 记录信息之后,由handler去处理

    • 日志过滤器 filter: 对记录信息进行过滤。

    • 日志格式化 formatter: 由处理器对记录的信息按formatter格式进行处理(除HTTPHandler和SMTPHandler之外)。

  • 核心方法或函数

    • logging.getLogger(name) # 默认没有name时,返回root

    • logging.baseConfig() 配置root记录器的格式、处理器等。

    • logging.info()/debug()/warning()/error()/critical() 由root记录器记录日志信息。

  • logger记录器的核心方法

    • setLevel(logging.DEBUG|INFO|WARNING|ERROR|FATAL)

    • addHandler(handler)

    • addFilter(Filter)

    • debug()|info()….

  • handler处理器的核心方法

    • setLevel(logging.DEBUG|INFO|WARNING|ERROR|FATAL)

    • setFormatter(fmt)

  • Formatter初始化参数

    • format 格式化的字符串, 使用%(日志变量)s 相关日志变量占位符组成的字符串

      'hi, %(name)s, age is %(age)s' % {'age': 20, 'name': 'jack'}
      'hi, %s, age is %s' % ('disen', 30)
    • datefmt 指定 %(asctime)s 日志时间的格式, 通常使用 %Y-%m-%d %H:%M:%S年月日 时分秒格式。

scrapy框架

五大组件两个中间件

  • engine 核心引擎

  • spider 爬虫类

  • scheduler 调度器

  • downloader 下载器

  • itempipeline 数据管道

  • 爬虫中间件、下载中间件

scrapy指令

  • scrapy startproject 项目名

  • scrapy genspider 爬虫名 域名

  • scrapy crawl 爬虫名

    • -o 保存数据到指定的文件中

    • -s 信号(CLOSESPIDER_ITEMCOUNT=30)

  • scrapy shell [url]

    • fetch(url)

    • view(response)

    • request: scrapy.http.Request

    • response: scrapy.http.Response|HtmlResponse

    • scrapy

Response对象的属性或方法

  • body|text|encoding|status|url|request|headers|meta

  • xpath()|css() -> scrapy.selector.SelectorList[Selector]

    • extract()

    • get()

    • extract_first()

  • css() 中表达式

    • 样式选择器[::text|attr("属性名")]

  • xpath()中表达式

    同lxml的xpath表达式相同。

Request初始化参

  • url

  • callback 如果未指定,则默认为 parse

  • priority 优先级的权限值, 值高优先级高

  • meta

  • headers

  • dont_filter 是否过滤重复的url, True不过滤,Flase过滤.

scrapy数据管道

指令方式存储

scrapy crawl 爬虫名 -o xxx.json|csv

只适合单页数据爬取,如果多页多层次数据爬取时,不适合此方式。

Item类

作用: 用于区别中哪一页(类型)的数据

用法: 类似于dict用法, 在数据管道类的process_item()方法中,通过isinstance()方法来判断item是哪一类型的。

import scrapy
​
class BookItem(scrapy.Item):
    book_id = scrapy.Field()
    book_name = scrapy.Field()
    book_cover = scrapy.Field()
    book_url = scrapy.Field()
    author = scrapy.Field()
    tags = scrapy.Field()
    summary = scrapy.Field()
​
​
class SegItem(scrapy.Item):
    book_id = scrapy.Field()
    seg_id = scrapy.Field()  # 章节ID
    title = scrapy.Field()
    url = scrapy.Field()
​
​
class SegDetailItem(scrapy.Item):
    seg_id = scrapy.Field()  # 章节ID
    content = scrapy.Field()  # 内容

Pipeline

  • 处理数据的方法

    def process_item(self, item, spider):
         return item
    • item参数表示 爬虫类中解释到的数据(yield item)

    • spider参数 表示爬虫类对象

    • 如果item被返回,则表示可以被优先级低的pipeline处理

  • 初始化方法

    属于定制方法,可以初始化一些参数或对象,如文件名, 数据库的连接等。

  • process_iteminit的调用次数说明

    • process_item方法 会被(engine)多次调用

    • init随着爬虫程序的启动时创建pipeline类时调用,只会被调用一次

定量爬虫

基于信号方式

scrapy crawl -s 信号

常用的scrapy信号

  • CLOSESPIDER_ITEMCOUNT=条目的数量

  • CLOSESPIDER_PAGECOUNT=请求页的数量

  • CLOSESPIDER_ERRORCOUNT=请求错误的数量

  • CLOSESPIDER_TIMEOUT=超时的时长

scrapy crawl wanben -s CLOSESPIDER_ITEMCOUNT=10

 

下载中间件

爬虫中间件

监测爬虫类与引擎之间的交互数据(请求 request、响应 response、数据item)及异常情况
@classmethod
def from_crawler(cls, crawler): pass  # 启动爬虫时用于创建爬虫中间件类的实例对象
​
def process_spider_input(self, response, spider) # 流程中第6步,engine将请求响应的数据输入给spider时,调用此方法。
​
def process_spider_output(self, response, result, spider) # 流程中第7步,由spider类解析response数据之后产生结果输出给engine时,调用此方法
​
def process_spider_exception(self, response, exception, spider): # 解析数据时发异常时
  
def process_start_requests(self, start_requests, spider): # 第一次爬虫发起请求时,调用此方法,即流程中第1步,从Spider->Engine时。

下载中间件 [重点]

下载中间件是引擎engine和下载器downloader之间的中间件,可以拦截请求和响应以及请求异常的处理。
@classmethod
def from_crawler(cls, crawler)
​
def process_request(self, request, spider)
​
def process_response(self, request, response, spider)
​
def process_exception(self, request, exception, spider)
  • process_request()方法可返回的对象(四种可能)

    • scrapy.http.Request

    • scrapy.http.HtmlResponse/Response

    • None 表示不拦截

    • raise IgnoreRequest 不下载这个请求

  • process_response()方法可以返回的对象

    • scrapy.http.Request 未下载成功请求

    • scrapy.http.Response 进一步封装之后的response

4.3 作用

在下载中间件中,可以设置代理、设置cookie、设置请求头以及基于Selenium实现动态js渲染和用户登录。

 

作业

  • 写出scrapy的工作流程中第3、6两个步骤的描述

    第3步: 引擎从调度器中获取下载任务, scheduler -> engine
    第6步: 引擎从下载器获取的响应传递给spider, 用于解析。engine-> spider。 ( response.request.callback(response) )
  • 写出logging.baseConfig()方法的参数(4+)

    logging.baseConfig(filename,  # 文件处理器参数
                       mode="a",  # 文件处理器参数
                       format,    # formatter
                       datefmt,   # formatter
                       handlers,  # addHandler(),
                       filters,   # addFilter()
                       stream)    # filename设置后, stream无效

     

  • 根据左边AA表结构和右边的查询结果,写出查询的SQL

    ---------------------------        ----------------------
    | year  |  month | amount |        | year  |  m1 |   m2 |
    ---------------------------        ----------------------
    | 1991  |    1   | 1.1    |        | 1991  | 1.1 | 1.2  |
    ---------------------------        ----------------------
    | 1991  |    2   | 1.2    |        | 1992  | 2.1 | 2.2  |
    ---------------------------        ----------------------
    | 1992  |    1   | 2.1    |
    ---------------------------
    | 1992  |    2   | 2.2    |
    ---------------------------
    • join连接表方式

      select a1.year, a1.amount as m1, a2.amount as m2
      from AA a1
      join AA a2 on (a1.year = a2.year)
      where a1.month=1
      and a2.month=2;
    • if/case判断函数方式

      select if(条件, 成立的结果, 不成立的结果);
      select year, 
      			max(round(if(month=1, amount, 0),1)) m1, 
      			max(round(if(month=2, amount, 0),1)) m2
      from AA
      group by year;
      select year, 
             max(round(m1, 1)) as m1,
             max(round(m2, 1)) as m2
      from (
          select year,
                 case when month=1 then amount else 0 end as m1,
                 case when month=2 then amount else 0 end as m2
          from AA
      ) a
      group by year;
      select year, 
             max(round(m1, 1)) as m1,
             max(round(m2, 1)) as m2
      from (
          select year,
                 case month when 1 then amount else 0 end as m1,
                 case month when 2 then amount else 0 end as m2
          from AA
      ) a
      group by year;

       

 

爬虫第八天

回顾知识点

数据处理

  • 启动爬虫的指令中带有 -o参数, 指定数据存储的文件(csv/json/xml)位置

  • 数据管道 pipeline

    • settings.py中配置管道类

    • process_item(self, item, spider) 管道类的处理数据的方法

      • 通过isinstance() 判断item属于哪一种类型, 然后按某一类型进行处理

      • 扩展 Python的自省相关函数

        type(obj)  获取对象的类型
        dir(obj)   获取对象中所有的属性(函数、类、变量)
        help(obj)  获取对象的文档帮助信息
        isinstance(obj, 类) 判断obj是否为类的实例对象
        issubclass(类, 父类) 判断类是否为父类的子类
        hasattr(对象, 属性或方法)  判断对象中是否存在属性或方法
        getattr(对象, 属性或方法)  获取对象中的属性或方法
        setattr(对象, 属性, 属性值) 设置对象的属性或方法
        id(对象) 获取对象在内存中的唯一标识
      • 返回item: 目的是让优先级低的数据管道类可以接收到item数据。

中间件

  • 爬虫中间件(scrapy工作流中第1, 第6, 第7等3个步)

    @classmethod
    def from_crawler(self, crawler)
    ​
    def process_spider_input(self, response, spider)
    ​
    def process_spider_output(self, response, result, spider)
    ​
    def process_spider_exception(self, response, exception, spider)
    ​
    def process_start_requetst(self, start_requests, spider)
    ​
    def spider_opened(self, spider)

     

  • 下载中间件 (第4, 5两个步骤)

    @classmethod
    def from_crawler(self, crawler)
    ​
    def process_request(self, request, spider):
      return None|Request|Response| raise IgnoreRequest
    ​
    def process_response(self, request, response, spider)
        return response|request
      
    def process_exception(self, request, excecption, spider)
    ​
    def spider_opened(self, spider)

 

规则爬虫

  • 创建规则爬虫的指令

    scrapy genspider -t crawl 爬虫名 域名
  • 链接提取器 LinkExctractor

    • 正则方式 (allow | deny)

    • restrict_xpaths() xpath方式指定a标签所在的(间接)父级标签

    • restrict_css() 样式方式指定a标签所在的(间接)父级标签

  • Rule() 规则

    • extractor: LinkExtractor

    • callback: str

    • follow=True 表示提取的连接在请求成功后,解析时是否继续按此规则提取连接

  • 不能重写parse()函数 【注】

规则爬虫【重】

LinkExtractor 类

作用: 提取感兴趣的a标签中的连接href属性, 因此在指定正则表过式,参考某些a标签中href属性的写法。如果正则提取困难,则支持css或xpath两个方式来指定a标签所在的父级标签。

核心的类

  • scrapy.spiders.CrawlSpider 规则爬虫类

    重写了parse()解析函数,在此函数中通过指定规则中的LinkExtractor对象来提取当前响应数据中的连接,并向engine发起新的请求。新的请求中包含提取的连接url和rule中的回调函数。

    def parse(self, response):
            return self._parse_response(response, 
                    self.parse_start_url, cb_kwargs={}, follow=True)
    ​
     
    def _parse_response(self, response, callback, cb_kwargs, follow=True):
        if callback:
            cb_res = callback(response, **cb_kwargs) or ()
            cb_res = self.process_results(response, cb_res)
            for requests_or_item in iterate_spider_output(cb_res):
                yield requests_or_item
    ​
        if follow and self._follow_links:
            for request_or_item in self._requests_to_follow(response):
                yield request_or_item
                
    def _requests_to_follow(self, response):
        if not isinstance(response, HtmlResponse):
            return
        seen = set()
        for n, rule in enumerate(self._rules):
            links = [lnk for lnk in rule.link_extractor.extract_links(response)
                     if lnk not in seen]
            if links and rule.process_links:
                links = rule.process_links(links)
            for link in links:
                seen.add(link)
                r = self._build_request(n, link)
                yield rule.process_request(r)    
  • scrapy.spiders.Rule 规则类

    • extractor: LinkExtractor

    • callback:str

    • follow:bool

  • scrapy.linkextractors.LinkExtractor 链接提取器类

    • allow

    • deny

    • restrict_xpaths

    • restrict_css

  • 创建规则爬虫是使用 -t crawl 模板

    scrapy genspider -t crawl 爬虫名 域名

图片管道

使用ImagesPipeline

  • settings.py配置

    • IMAGES_STORE 指定数据存放的位置

    • ITEM_PIPELINES字典中,引入scrapy.pipelines.images.ImagesPipeline

    • 配置缩略图

      IMAGES_THUMBS = {
           'small': (with, height),
           'big': (widht, height)
      }

       

  • item数据属性

    • image_urls: list 表示下载图片地址的列表

    • images: list 表示下载完成后的图片存放在IMAGES_STORE中的路径及相关的属性

 

自定义ImagesPipeline

实现ImagesPipeline的子类,重写三个核心的方法。

三个核心的方法:

  • get_media_requests(self, item, info) 根据item中图片连接的属性,返回图片下载的request。

    可以返回一个Request也可以多个Request的列表

  • file_path(self, request, response, info) 根据请求和响应返回图片保存的位置(相对于IMAGES_STORE)。

    如果返回的路径中包含子路径时,系统会自动创建子目录( os.makedirs() )。

  • item_completed(self, results, item, info) 图片下载完成后,从results的结果获取图片的保存路径,并设置到item中,最后返回这个item。

    results格式

    [
       (True,  {'path': '', 'url': '', chucksum: ' '} ),
       (True,  {'path': '', 'url': '', })
    ]

     

其它技术点

日志

scrapy的日志记录器在爬虫类对象中, 是logger, 通过爬虫对象的logger来记录运行的信息。scrapy的logger日志记录器使用了Adpater设计模式的logging.LoggerAdapter类。

【扩展】构建器设计模式 Builder ( 函数式-流式编程 )

car: Car = CarBuilder().step1().step2().step3().step4().build()
text = response.xpath().css().xpath().css().xpath().get()

Python开发人员需要掌握的设计模式: 单例、工厂、装饰器、适配器、构建器、生产者消费者(消息队列-订阅/发布)。

配置

在settings.py文件中,指定收集日志的等级及日志存储的文件名

LOG_LEVEL = ''  # DEBUG|INFO|ERROR|WARNING|CRITICAL
LOG_FILE = '文件名'  # os.path.join(BASE_DIR, 'access.log')

在程序中使用

  • 爬虫日志记录器

spider.logger.info()/error()
  • 获取scrapy中其它的记录器

    • 内置的记录器

    - scrapy.utils.log
    - scrapy.crawler
    - scrapy.middleware
    - scrapy.downloadermiddlewares.httpauth|downloadtimeout...
    - scrapy.core.engine
    - scrapy.utils.signal
    logging.getLogger('scrapy.utils.log').info()/warning()
  • 自定义记录器

    error_logger = logging.getLogger('dushu-project')
    error_logger.setLevel(logging.ERROR)
    
    handler = logging.FileHandler(
        os.path.join(BASE_DIR, 'error.log'),
        encoding='utf-8'
    )
    handler.setLevel(logging.ERROR)
    handler.setFormatter(logging.Formatter(
        '%(asctime)s %(name)s at %(lineno)s of %(pathname)s : %(message)s'
    ))
    error_logger.addHandler(handler)

    使用时

    from dushu.settings import error_logger
    
    error_logger.error('消息')

post请求

  • scrapy.http.FormRequest

    • url

    • formdata: dict( value必须都是字符串类型)

    • cookies: dict

    • headers: dict

    • callback 默认parse

  • 如果爬虫中第一次的请求是post请求,则重写Spider类的start_requests()方法

     def start_requests(self):
            self.url = 'http://ccgp-shaanxi.gov.cn/notice/noticeaframe.do?noticetype=3&province=province&isgovertment='
            self.data = {
                'page.pageNum': '1'
            }
            self.MAX_PAGE = 1399
            yield FormRequest(self.url, formdata=self.data)

    不需要指定start_urls 列表。

Selenium中间件

作用: 动态渲染js(ajax加载数据)

4.3.1 定义下载中间件类

class LoadDataMiddleware():
    @classmethod
    def from_crawler(cls, crawler):
        # This method is used by Scrapy to create your spiders.
        s = cls()
        crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
        crawler.signals.connect(s.spider_closed, signal=signals.spider_closed)

        return s
  • 连接打开爬虫类的信号,在处理函数中打开chrome

    def spider_opened(self, spider):
        # 创建Selenium的Chrome浏览器对象
        # chromedriver.exec 所在的目录已配置到环境变量Path中
        options = Options()
        options.add_argument('--headless')
        options.add_argument('--disable-gpu')

        self.chrome = Chrome(options=options)
  • 连接关闭爬虫类的信号,在处理函数中退出chrome

  • def spider_closed(self, spider):
           self.chrome.quit()
  • 处理process_request()方法编写核心业务

     def process_request(self, request, spider):
            # 判断是否为第一次请求
            if not request.meta.get('next_page', False):
                self.chrome.get(request.url)
            else:
                # 点击下一页
                self.chrome.execute_script('var q=document.documentElement.scrollTop=1200')
                time.sleep(0.5)
                self.chrome.execute_script('var q=document.documentElement.scrollLeft=1000')
                time.sleep(0.5)
                self.chrome.find_elements_by_xpath('//ul[@class="pagination"]/li/a')[-2].click()
    
            time.sleep(2)
            html = self.chrome.page_source
            return HtmlResponse(request.url, body=html.encode('utf-8'))

配置

DOWNLOADER_MIDDLEWARES = {
   'caigou.middlewares.LoadDataMiddleware': 543,
}

爬虫类

class ShaanxiSpider(scrapy.Spider):
    name = 'shaanxi2'
    allowed_domains = ['ccgp-shaanxi.gov.cn']
    # start_urls = ['http://ccgp-shaanxi.gov.cn/notice/noticeaframe.do?noticetype=3&province=province&isgovertment=']
    start_urls = ['http://ccgp-shaanxi.gov.cn/notice/list.do?noticetype=3&province=province']

    def parse(self, response):
        trs = response.css('.list-box tbody tr')
        for tr in trs:
            item = {}
            item['id'] = tr.xpath('./td[1]/text()').get()
            item['area'] = tr.xpath('./td[2]/text()').get()
            item['title'] = tr.xpath('./td[3]/a/text()').get()
            item['url'] = tr.xpath('./td[3]/a/@href').get()
            item['date'] = tr.xpath('./td[4]/text()').get()

            yield item

        # 获取下一页数据
        if len(trs) == 15:
            yield Request(response.request.url,
                          meta={'next_page': True}, dont_filter=True)

dont_filter=True 原因是可能会被认为是重复, 让engine不去过滤重复的url。

作业

  • 定量爬虫的指令有哪些

    CLOSESPIDER_ITEMCOUNT  item数据条目量
    CLOSESPIDER_PAGECOUNT  请求成功响应的次数
  • 下载中间件类的处理请求的方法是什么,可以返回哪些对象

    def process_request(self, request, spider):
        return None|Request|Response | rasie IgnoreRequest
    • None 表示不拦截当前的请求

    • Request 重新返回新的请求, engine将这个新请求压入到Scheduler中

    • Response 表示不需要下载器下载,由自己程序下载并且封装成Response对象。

    • raise IgnoreRequest 取消当前的request请求(重复, 过滤的)

  • 简述中间件from_crawler(self, crawler)函数的作用

    当爬虫程序启动后,用于创建当前中间件类实例的方法。
    可以监听爬虫程序是否正常启动。

    【扩展】类方法、静态方法和实例方法的区别??

    类方法:类对象(由元类创建类的对象)的方法,第一个参数是cls(表示当前类本身)。
    	     @classmethod 修饰的方法
    类实例方法: 由类的__new__()函数处理的类实例对象的方法,第一个参数是self(表示当前类的实例)
    静态方法: 和类没有任何关系,只是在当前类中声明,方法的参数不会出现cls和self。
             @staticmethod 修改的方法

    【扩展】什么是抽象方法?

    抽象方法是在父类中声明(没有实现功能),由子类实现的方法。如果子类中未实现,则会在调用时报错。
    class Animal():
      def eat(self):  # 抽象方法
         raise Exception('子类中必须实现此方法!')
    class Pig(Animal):
       def eat(self):
          print('Pig eat ', 'ddd')
          
    class Dog(Animal):
       def eat(self):
          print('Dog eat ', 'abc')

     

爬虫第九天

回顾知识点

规则爬虫

  • scrapy.spiders.CrawlSpider 所有规则爬虫类的父类

  • scrapy.spiders.Rule 规则类

    • LinkExtractor 连接提取类的实例对象

    • callback: str 指定提取器提取的连接请求成功之后的数据解析的函数

    • follow: bool 表示对提取连接请求成功后的数据是否继续提取连接

  • scrapy.linkextractors.LinkExtractor

    • allow: str 提取连接中的href的正则表示

    • deny: str 拒绝提取连接中的href的正则表示

    • restrict_xpaths 以xpath的路径方式指定a所在的父级标签

    • restrict_css 以css的样式选择器方式指定a所在的父级标签

  • 创建规则爬虫的指令

    scrapy genspider -t crawl 爬虫名 域名

日志

  • logging.LoggerAdapter 日志适配器类, 封装日志记录器和spider等相关消息

  • 每个爬虫类都存在它的日志记录器,记录器名称即是爬虫名(self.name)

  • 在中间件或管道等相关处理数据(item/request/response)的方法中都存在spider对象

    spider.logger.info()/error()..记录日志
  • 在settings.py中配置日志记录的等级及日志文件存储的位置

    LOG_LEVEL='INFO'
    LOG_FILE = '/var/log/xxx.log'
  • 可以自定义日志记录器(可以声明一个日志处理模块-log_

    dushu
      |---dushu
              |---spiders
              |---item.py
              |---middleware.py
              |---pipeline.py
              |---settings.py
              |---log_
                    |--__init__.py
      |---scrapy.cfg

    log_.__init__.py文件中的内容:

    import logging
    from logging import Formatter, FileHandler
    ​
    logger = logging.getLogger('dushu')
    logger.setLevel(logging.INFO)
    ​
    handler = FileHandler('/var/log/dushu.log')
    handler.setLevel(logging.INFO)
    handler.setFormatter(Formatter(format='%(asctime)s:  %(message)s', 
                                   datefmt='%Y-%m-%d %H:%M:%S'))
    ​
    logger.addHandler(handler)
    from dushu.log_ import logger
    ​
    logger.info('记录的信息')

ImagesPipeline

  • scrapy.pipelines.images.ImagesPipeline 配置在settings.py的ITEM_PIPELINES={}

  • 在settings.py文件配置图片存储的位置

    IMAGES_STORE = '/var/src/images'
  • 在爬虫类的parse()中,指定item的image_urlsimages

  • 自定义ImagesPipeline

    • 声明ImagesPipeline的子类

    • 重写get_media_requests(self, item, info)方法, 在此方法中获取item的图片下载地址,返回相关的Request(url, meta={'name': item['name']})

    • 重写file_path(self, request, response, info)方法, 在此方法中返回相对于IMAGEs_STORE图片存储的相对路径

      return '%s/%s.jpg' %(dir_name, file_name)
    • 重写item_completed(self, results, item, info),在此方法中,从results中获取下载文件存储的路径, 并添加到item中

      path = [ data['path'] for ok, data in results if ok]
      item['path'] = ','.join(path)
      
      return item

Selenium下载中间件

  • 声明一个类, 并将此类配置在DOWNLOADER_MIDDLEWARES={}

  • 声明from_crawler(self, crawler) 创建当前中间件类的实例的, 在此方法中指定spider_openedspider_closed两个信号的处理方法

    • 在spider_opened()方法中,创建Chrome浏览器的驱动对象

    • 在spider_closed()方法, 将chrome浏览器对象进行退出

  • 声明process_request(self, request, spider) 处理每个请求

    self.chrome.get(request.url)
    # ....等待某个UI元素出现
    
    html = self.chrome.page_sources
    
    return scrapy.http.HtmlResponse(request.url, body=html.encode('utf-8'))

     

分布式爬虫

什么是分布式

  • Hadoop 分布式计算框架(大数据处理)HDFS(分布式文件系统)

    • MapReduce

    • Hbase 数据库

    • Hive 实时数据库

    • Spark 大数据平台(MySQL、Hbase)

  • 由多个服务器(操作系统-PC)组成,在调度器调度的情况下完成不同的任务,这种架构称之为分布式。常见的调度器是消息中间件、服务注册中心、负载均衡等组成。

常见消息队列

  • Redis订阅与发布-实现消息队列

  • RabbitMQ 基于Channel实现消息队列

  • Kafka 消息队列

scrapy-redis

  • 安装包: pip install scrapy-redis

  • 配置调度器类、去重类及消息中间件(队列的位置)

    SCHEDULER="scrapy_redis.scheduler.Scheduler"
    SCHEDULER_PERSIST=True
    DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
    
    REDIS_URL='reids://[:password@]host-ip:6379/1' # 0-15数据库索引
  • 修改爬虫类

    • 父类 scrapy_redis.spiders.RedisSpider|RedisCrawlSpider

    • 去掉start_urls 列表

    • 增加redis_key 字符串变量, 指定redis服务中存储的key

  • 按正常启动爬虫程序命令启动爬虫

    scrapy crawl 爬虫名
  • 连接redis服务,向redis_key的列表list中推送请求任务

    lpush xxxx http://www.xxxx.com/xxx/

     

爬虫程序部署

scrapyd

  • 安装scrapyd服务和客户端

    pip install scrapyd scrapyd-client
  • 如果在云服务器安装scrapyd

    • 修改scrapyd源码中app.py文件

      bind_addres = '0.0.0.0'
    • 在云服务的安全组中,放开6800端口

  • 在scrapyd服务启动Python环境中安装爬虫需要的依赖库

  • 修改爬虫项目scrapy.cfg文件

    [deploy:100]
    url = http://119.3.170.97:6800/
    project = dushu_redis
  • 使用scrapyd-deploy命令发布项目

    scrapyd-deploy 100 -p dushu_redis
  • 通过scrapyd的接口启动和停止爬虫

docker部署

编写Dockerfile文件

FROM 119.3.170.97:5000/ubuntu:latest
MAINTAINER disen 610039018@qq.com
ADD . /usr/src
WORKDIR /usr/src
VOLUME /usr/src
RUN pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple
RUN chmod +x run.sh
CMD /usr/src/run.sh
  • ADD 命令将当前目录下所有文件复制到容器的/usr/src目录下

  • WORKDIR 容器中切换当前的工作目录,类似于cd

  • VOLUME 将容器中的文件位置暴露给外部宿主机, 在启动镜像时,通过 docker run -v同步宿主和容器之间的文件目录

  • RUN 执行容器中的命令, 可以执行多次

  • CMD 是当容器启动时执行的命令,且Docker中只能使用一次

编写shell文件

#!/bin/bash
cd /usr/src
scrapy crawl guoxue

构建镜像

在执行docker build之前,需要将sh文件改为可执行, 且文件格式改为UNIX。

vi run.sh文件,按shift+:进入命令行,执行以下指令:

:set ff=unix
docker build -t dushu:1.0 .

成功之后,查看镜像是否存在

docker images

启动镜像

docker run -itd --name spider_dushu -v /root/dushu_spider:/usr/src dushu:1.0

查看日志是否启动

cat dushu.log
docker exec spider_dushu cat dushu.log
docker stop spider_dushu

定时器

while循环

crontab

vi /root/runspider.sh

#!/bin/bash
  
# cd /root/spider_dushu
# source /root/venvs/dushu/bin/activate
# scrapy crawl guoxue

docker start spider_dushu                      
chmod +x runspider.sh
ln -s /root/runspider.sh /usr/bin/run_dushu

测试命令

run_dushu

编辑定时任务, vi /root/dushu.cron

每半小时(30分)执行一次 run_dushu命令

30 * * * * run_dushu

格式: 分 时 天 月 周

添加定时任务

crontab dushu.cron 
crontab -l

查看定时任务是否添加成功

作业

  • 在scrapy框架中如何记录日志信息

    spider.logger.info()/error()/warning()/critical()
    LOG_LEVEL = 'INFO'
    LOG_FILE = 'xxx.log'
  • 使用ImagesPipeline时需要注意哪些事项

    settings.py

    IMAGE_STORE=''

    Spider类的parse()函数中

    item['image_urls']
    item['images']
  • 写出scrapy中哪些地方出现优先级,分别有什么不同

    # 请求优先级
    yield Request(url, callback, priority=10)
    # settings.py
    # 中间件和管道优先级
    ITEM_PIPELINES = {
        'dushu.pipelines.DBPipeline': 300
    }
    
    DOWNLOADER_MIDDLEWARES = {
        'xxx.xxx.LoadDataMiddleware': 100
    }

    请求优先级数值越高,优先级越高;

    中间件和管道优先级的数值越小,优先级越高。

  • 爬取机票信息

    • 获取所有城市名称与编号

    • 获取近一个月【西安】-【北京】航班信息(班次、时间节点和价格)

  • 总结近一周的爬虫知识点

  • 重点掌握requests接口请求和docker部署爬虫项目

  • 完成SQL练习题中10道题。

 

爬虫第十天

回顾爬虫技术

网络请求

  • urllib库

    • request.urlopen()|urlretrieve()|Request|build_opener()|HTTPHandler|HTTPCookieProcessor(http.cookiejar.Cookiejar())|ProxyHandler(proxies={})

    • parse.quote()|urlencode()

  • requests库(接口测试)

    • 依赖urllib3(封装了很多类-OOB)

    • request(method, url, params, data, json, files, headers,cookies, auth, proxies)

    • get(url, params, headers, proxies)

    • post(url, data, json, headers, proxies, cookies,files)

    • put(url, data, json, headers, proxies, cookies, files)

    • delete(url)

    • session() 用于存储Cookie, 可以与服务器建立长连接Connection: keep-alive请求或响应头。

  • 请求头和响应头

    • 请求头

      # 请求头原始报文的第一行
      # GET / HTTP/1.1
      HOST: www.baidu.com
      Accept: text/html,text/*
      Referer: 
      X-Requested-with: XMLHTTPRequest
      User-Agent: 
      Cookie: 
      Content-Type:
      Content-Length:
    • 响应头(Web后端服务)

      # 原始报文的第一行
      # HTTP/1.1 200 OK
      Content-Type: text/html;charset=utf-8
      Content-Length:
      Set-Cookie: 
      Date:
      Server: 
      Cookie: 

数据解析

  • re解析提取

    • re.search()

    • re.findall()

  • xpath提取(Element)

    • rootElement = lxml.etree.HTML(html_content) 节点对象

    • 节点对象的.xpath()提取数据

      • /a/b/c

      • //div/ul//a

      • ./li/a/@href | ./li/a/text()

      • ./li[1]

      • ./li[position()<4]

      • ./li[last()-1]

      • //div[@class="abc"]

      • //div[starts-with(@class, "abc")]

      • //div[ends-with(@class, "ddd")]

  • bs4( Tag )

    • rootTag = BeautifulSoup(html_content, 'lxml')

    • find('标签名', class_|id_)|find_all()

    • selector(CSS选择器)

    • bs4.element.Tag对象的属性

      • text|string|get_text()

      • attrs|Tag[]|Tag.get("属性名", 默认属性值)

      • contents 所有的文本子节点

      • descendants 所有子标签节点对象

数据存储

  • pymysql

  • csv (csv.DictWriter|csv.DictReader)

  • json

  • excel( xlwt|xlrd )

爬虫框架

  • Selenium (UI自动化测试工具)

    • 网络请求

    • 元素查找

      • find_element[s]_by_id|name|class_name|tag_name|xpath|css_selector()|link_text()

      • find_element(By, value) 查找第一个元素

        selenium.webdriver.common.by.By

      • find_elements(By, value) 查找所有元素

    • 事件交互(输入、点击、滚动、截屏、切换窗口)

    • 等待UI元素出现(用于等待Ajax加完数据)

      • selenium.webdriver.support.ui

      • selenium.webdriver.support.excepted_conditions as ec

      ui.WebDriverWait(driver, timeout).until(
         ec.visibility_of_all_elements_located(
          (By, value)
         ),
         timeout_msg
      )
  • Scrapy|Scrapy-Redis

    • 五大核心组件两个中间件

      - Engin
      - Scheduler
      - Spider 
      - Downloader
      - ItemPipeline
      SpiderMiddlerware
          - process_spider_input(self, response, spider)
          - process_spider_output(self, response, results, spider)
          - process_spider_exception(self, resposne, exct, spider)
          - process_start_requests(self, start_reques
          ts, spider)
      DownloaderMiddleware
          - process_request(self, request, spider)
          - process_response(self, request, response, spider)
          - process_exception(self, request, excpt, spider)

      两个中间件都存在的方法

      @classmethod
      from_crawler(cls, crawler)
    • 解析数据时

      • response.css()|xpath() 返回SelectorList或Selector

      • Selector对象的方法

        • get()

        • extract()

        • extract_first()

        • css()|xpath()

    • scrapy.Response对象的属性

      • status

      • encoding 可以指定字符集编码

      • headers

      • cookies

      • text

      • body

    • scrapy.Request初始化参数

      • url

      • callback 指定回调函数

      • meta 向parse解析方法回传数据的dict类型的元数据

      • headers

      • cookies

      • priority 请求优先级

      • dont_filter 是否检查过滤重复的URL

    • 两个爬虫类

      • scrapy.Spider

        • 重写parse()

        • 指定start_urls = []

      • scrapy.spiders.CrawlSpider

        • 指定start_urls-> list 和 rules -> tuple

        • scrapy.spiders.Rule类初始化参数

          • link_extractor

            • scrapy.linkextractors.LinkExtractor 初始化参数

              • allow

              • deny

              • restrict_xpaths

              • restrict_css

          • callback: str

          • follow: bool

  • 反爬虫的策略

    • UA (百度、安居)

    • Cookie

    • Referer(读书网的图片资源下载)

    • IP代理

    • 字体CSS加密(大众点评)

    • 图片验证码

    • 滑块验证码

    • 动态JS

    • 短信验证码

  • 分布式爬虫

    • scrapy-redis 消息中间件使用redis

  • 爬虫的部署【Linux熟悉】

    • 云服务器部署

    • docker部署

    • scrapyd部署

mongodb

docker部署

docker pull mongo
docker run -itd --name mongo_server1 -p 27017:27017 mongo

如果是在云服务启动的,则在服务器的安全组规则中添加27017端口访问的规则。

数据结构

  • 文档: 指一条数据, 在javascript的以js对象来表示,在python以dict对象来表示。

  • 集合: 指多条数据组成的对象,在javascript中以js数组表示,在python以list对象表示。

  • 数据库: 多个集合组成了库

 

常用操作

  • show dbs

  • show collections

  • use dushu 打开或创建dushu数据库

  • db.createCollection('集合名')

  • db.集合名.drop()

  • db.dropDatabase()

  • db.集合名.insert()|save()

  • db.集合名.update(条件{}, 更新的数据{ $set: { }}, upsert, multi)

    • upsert 为真时,当条件没有匹配数据时,将更新的数据插入到集合中, 反之为假时,则什么也不做。

    • multi, 为真时, 当条件匹配多条数据时,将会更新所有的数据,反之,只更新每一条数据。

  • db.集合名.remove(条件 { })

    删除user集合中文档的name属性包含所有的记录

    db.user.remove({name: {$regex: '成'}})
  • db.集合名.find(条件{ }, 保留属性{name: 1}).pretty()

    • 逻辑关系

      • $or

      • $ne

      • $lt

      • $gt

      • $lte

      • $gte

    • 正则表示

      • $regex

作业

  • 写出urllib、requests和scrapy的response响应对象的类及属性

    • urllib: http.client.HTTPResponse

      • code

      • getheader(name, default_value)

      • getheaders()

      • read()|readline()|readlines()

    • requests: requests.Response

      • status_code

      • headers

      • encoding

      • cookies

      • text|contents

    • scrapy: scrapy.http.Response

      • status

      • headers

      • cookies

      • encoding

      • text|body

      • request

      • meta

      • url

  • 写出lxml的xpath和scrapy的xpath的不同之处

    • lxml的xpath

      • 返回元素对象是Element, list[Element, Element, ]

      • 提取元素属性或文本时,返回list['', '']

    • scrapy的xpath

      • 返回元素对象是SelectorList或Selector

      • 提到元素的属性或文本时,返回SelectorList或Selector

      • 元素对象提取数据的方法: .get()|extract()|extract_first()

  • 写出Dockerfile的RUN和CMD的用法与区别

    • RUN 在容器中执行普通的命令, 在Dockerfile中可以多次使用

    • CMD 在容器启动时执行的命令, 在Dockerfile只能执行一次(最后一个命令)

  • 基于flask实现笑话网的查询、推荐接口(5个笑话)

    搜索页面: 类似于百度首页

    推荐接口: 根据搜索记录,推荐5个笑话

  • Docker实现MongoDB的集群

  • 整理2周爬虫的知识点

  • 复习Shell脚本编程

  • 安装Anaconda环境,自我学习Conda命令

  • 提前学习jupyter notebook的常见的快捷方式

 
行业资料:添加即可领取PPT模板、简历模板、行业经典书籍PDF。
面试题库:历年经典,热乎的大厂面试真题,持续更新中,添加获取。
学习资料:含Python、爬虫、数据分析、算法等学习视频和文档,添加获取
交流加群:大佬指点迷津,你的问题往往有人遇到过,技术互助交流。

领取 


posted @ 2021-10-12 16:05  白云吃辣条  阅读(215)  评论(0编辑  收藏  举报