两周从爬虫小白变大神,看完你就知道我不是标题党了【五万字教程,建议收藏】
目录
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模块
简单的请求
urlopen(url, data=None)可以直接发起url的请求, 如果data不为空时,则默认是POST请求,反之为GET请求。
resp是http.client.HTTPResponse类对象。
带请求头的请求
任务1:收集Http协议的报文头的哪些Key
urllib.parse模块
此模块有两个核心的函数:
-
quote() 仅对中文字符串进行url编码;
-
urlencode() 可以针对一个字典中所有的values进行编码,然后转成key=value&key=value的字符串。
HTTP处理器
urllib的请求处理器,主要用于urllib.request.build_opener()
函数参数,表示构造一个由不同处理组成的伪浏览器。
HTTPHandler
处理Http协议的请求处理。
5HTTPCookieProcessor
处理Cookie的处理器,创建类实例时,需要提供http.cookiejar.CookieJar
类的实例对象。
ProxyHandler
作业
-
写出Python上下文的两个核心函数
-
写出正则中的(), [] , {} 三个符号的作用
-
写出pymysql.Connect()连接数据库的核心参数
-
豆瓣动作电影排行榜
-
肯德基店铺位置
爬虫第二天
回顾知识点
核心的网络请求库 -> 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': '
-
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-Type
为multipart/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
-
-
买家秀的模特的所有图片, 图片的名称是姓名-序号, 如'Disen-1.jpg', 'Disen-2.jpg'
-
中国图书网
爬虫所有的小说的基本信息(名称、作者、出版社、原价、折扣价、活动标签、 简介)
扩展
-
在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()
-
-
基于Flask实现文件上传服务器, 通过requests测试文件上传接口。
-
优化美女网爬虫,将数据存到es搜索引擎中
-
完成站长之家的多任务爬虫的数据存储(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的接口:
接口的参数:
-
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_item
和init
的调用次数说明-
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_urls
和images
-
自定义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_opened
和spider_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的接口启动和停止爬虫
-
-
post请求
-
project 项目名参数
-
spider 爬虫名参数
请求成功后,返回json数据,包含job_id数据,用于停止爬虫。
-
-
-
post请求
-
job 队列的ID
-
-
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、爬虫、数据分析、算法等学习视频和文档,添加获取
交流加群:大佬指点迷津,你的问题往往有人遇到过,技术互助交流。
领取