爬虫总结
一、
1.什么是爬虫?
爬虫就是:模拟浏览器发送请求,获取响应
2.爬虫的分类,爬虫的流程
- 聚焦爬虫:针对特定的网站的爬虫
- 准备url地址 -->发送请求 获取响应–> 提取数据–> 保存
- 获取响应–> 提取url地址,继续请求
- 通用爬虫:搜索引擎的爬虫
- 抓取网页–> 数据存储–>预处理–> 提供检索服务,网站排名
3.浏览器发送请求的过程
- 爬虫请求的:url地址对应的响应
- 浏览器获取到的内容:elements的内容=url对应的响应+js+css+图片
- 爬虫获取的内容和elements内容不一样,进行数据提取的时候,需要根据url地址对应的响应为准进行数据的提取
4.http的请求头user-agent有什么用
- user-agent:告诉对方服务器是什么客户端正在请求资源,爬虫中模拟浏览器非常重要的一个手段
- 爬虫中通过把user-agent设置为浏览器的user-agent,能够达到模拟浏览器的效果
- cookie:获取登录只有才能够访问的资源
5.利用requests模板如何发送请求和获取响应
- response = requests.get(url)
- response.text -> str # 根据响应信息进行有规律的推测网页的编码
- response.encoding=“utf-8”
- response.encoding=”gbk”
- response.content -> bytes
- response.content.decode(“utf8”)
# 发送请求,获取响应
def parse(self, url, data):
response = requests.get(url,params=params,headers=self.headers)
response = requests.post(url,data=data, headers=self.headers)
return response.content.decode()
- response.status_code
- response.request.headers
- response.headers
- 一般来说名词,往往都是对象的属性,对应的动词是对象的方法
- 获取网页源码的通用方式:
- response.content.decode() 的方式获取响应的html页面
- response.content.decode(“GBK”)
- response.text
6.python2和python3中的字符串
ascii 一个字节表示一个字符
unicode 两个字节表示一个字符
utf-8 边长的编码方式,1,2,3字节表示一个字符
- python2
- 字节类型:str,字节类型,通过decode()转化为unicode类型
- unicode类型:unicode ,通过encode转化为str字节类型
- python3
- str:字符串类型,通过encode() 转化为bytes
- bytes:字节类型,通过decode()转化为str类型
7.常见的状态响应码
- 200:成功
- 302:临时转移至新的url
- 307:临时转移至新的url
- 404:not found
- 500:服务器内部错误
二、
1.requests中headers如何使用,如何发送带headers的请求
- 模拟浏览器,欺骗服务器,获取和浏览器一致的内容
- headers = {“User-Agent”:“从浏览器中复制”}
- headers = {
“Origin”: “http://ntlias-stu.boxuegu.com”,
“Referer”: “http://ntlias-stu.boxuegu.com/”,
“User-Agent”: “Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.17 Safari/537.36”
}
- requests.get(url,headers=headers)
2.发送带参数的请求
params = {"":""}
url_temp = “不完整的URL地址”
requests.get(url_temp,params=params)
3.requests如何发送post请求
data = {“从浏览器中form data的位置寻找”}
requests.post(url,data=data)
4.requests中如何使用代理,使用代理的目的,代理的分类
- proxies = {“https”: “https://117.127.0.195:8080”}
- proxies = {协议:协议+ip+端口}
- requests.get(url,proxies=proxies)
目的:
代理的分类
- 高匿名代理:不知道在使用代理
- 匿名代理:知道在使用代理,但是不知道真实ip
- 透明代理(Transparent Proxy):对方知道真实的ip
5.requests中session类如何使用,为什么要使用session
- session = requests.Session()
- session.post(url,data) #cookie会保存在session中
- session.get(url) #用session发送请求会带上之前的cookie
- 注意:这块的session类和之前所学的session无任何关系
6.列表推导式
In [41]: [i for i in range(10)]
Out[41]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
In [42]: [i/2 for i in range(10)]
Out[42]: [0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5]
In [43]: [i/2 for i in range(10) if i%2==0]
三、
1.requests进行携带cookie登录
- cookie字符串放在headers中
- 把cookie字典交给requests请求方法的cookies
2.寻找登录接口的方法
- form表单action对应的url地址
- 用户名和密码的input标签中,name的值作为键,用户名和密码作为值的字典,作为post data
- 通过抓包,定位url地址
3.分析js,获取加密的数据
- 观察变化
- 定位js
- 通过event listener定位js的位置
- 通过搜索url地址中的关键字,通过chrome的search all file来进行搜索
- 进行分析
- 执行js
4.requests处理ssl证书
requests.get(url,verify=False)
5.获取响应中的cookie,转化为字典
- response = requests.get(url,headers=headers)
- requests.utils.dict_from_cookiejar(response.cookies)
6.requests中超时参数的使用,retrying模块的使用
- from retrying import retry
- requests.get(url,timeout=3)
- 通过装饰器的方式使用retry,进行异常捕获,重新执行被装饰的函数
from retrying import retry
@retry(stop_max_attempt_number=3)
def fun():
pass
7.数据的分类
- 结构化数据 json, xml
- 非结构化数据 html
8.json模块的使用
- 数据交换格式
- json.loads(json_str) json字符串转化为python类型
- json.dumps(python_type,ensure_ascii=False,indent=2) python类型转化为json字符串
- json.load() 把包含json的类文件对象中的数据提取出来转化为python类型
- json.dump() python类型存入类文件对象中
- 那么对于为什么需要模拟登陆?
获取cookie,能够爬取登陆后的页面
9.requests模拟登陆的三种方法
- session
- 实例化对象
- session.get(url) #cookie保存在session中
- session.get(url) #带上保存在session中cookie
- cookie方法headers中
- cookie传递给cookies参数
- cookie = {“cookie 的name的值”:“cookie 的value对应的值”}
10.三元运算符
a = 10 if 3<2 else 100
11.字典推导式
In [8]: {i:i+10 for i in range(10)}
Out[8]: {0: 10, 1: 11, 2: 12, 3: 13, 4: 14, 5: 15, 6: 16, 7: 17, 8: 18, 9: 19}
In [9]: {i:i+10 for i in range(10) if i%2==0}
Out[9]: {0: 10, 2: 12, 4: 14, 6: 16, 8: 18}
四、
1.正则的语法
- 字符
- . 能够匹配\n之外的所有字符 re.S模式下可以匹配\n
- \ 转义
- [] 或的效果,从中选择一个, [abc]+ 能够匹配多个
- | 或的效果
- 预定义的字符集
- \d 数字
- \s 空白字符串,包含空格、\n,\t
- \w 单词字符,a-zA-Z0-9_
- 数量词
2.re模块的常用方法
- re.findall(“正则表达式regex”,“待匹配的字符串”) # 返回列表,或者是空列表
- re.sub(“regex”,"_",“待替换的字符串”) # 返回字符串
- p = re.compile(“regex”,re.S/re.DOTALL) # 返回一个p模型,编译,提高匹配效率
- p.findall(“待匹配的字符串”)
- p.sub("_",“待替换的字符串”)
注:re.S 匹配\n re.DOTALL 匹配Tab键
3.原始字符串r
- 定义:相对于特殊符号而言,表示特殊符号的字面意思
- 用途:
- 正则中,能够忽略转义符号带来的影响,待匹配的字符串中有几个\,正则表达式中加上r,照着几个\即可
- windows文件路径
4.xpath语法
- xpath的安装 pip install lxml
- // 的用途
- //a html中所有的a
- div//a div中所有的a,包括div下的后代节点中的a
- a//text() a下的所有的文本
- @ 的使用
- a/@href 获取a的href的值
- //a[@class=‘b’]
- text() 的使用
- //a/text() 获取所有的a下的文本
- //a[text()=‘下一页’] 获取文本为下一页的a标签
- a//text() a下的所有的文本
- xpath包含的语法
- //div[contains(@class,“i”)] class包含i的div标签
- //a[contains(text(),“下一页”)] 文本包含下一页的a标签
- 兄弟标签
- xpath 选择特定位置
- //a[1] 第一个
- //a[last()] 最后一个
- //a[last()-1] 倒数第二个
- //a[postion()<4] 前三个
5.lxml模块的使用
from lxml import etree
element = etree.HTML(bytes/str) #返回element
ret_list = element.xpath("xpath字符串") #返回列表
bytes = etree.tostring(element) #返回bytes类型字符串
#数据提取时:先分组,再提取
五、
1.xpath包含的语法
//div[contains(@class,“i”)] class包含i的div标签
//a[contains(text(),“下一页”)] 文本包含下一页的a标签
2.url地址解码的方法
- requests.utils.unquote(url)
3.准备url地址
- 知道url地址的规律,知道一共多少页,准备url列表,果壳,糗百
- 不知道url地址规律,或者不知道一共多少页,准备start_url ,贴吧
4.多线程爬虫
- threading
- t1 = threading.Thread(targe=func,args=(,))
- t1.setDaemon(True) #设置为守护线程
- t1.start() #此时线程才会启动
- 队列
- from queue import Queue
- q = Queue()
- q.put() 队列计数+1
- q.get() 队列计数不会-1
- q.task_done() 和get()方法配合,队列计数-1
- q.join() #阻塞主线程,让主线程等待队列任务结束之后在结束,队列任务在计数为0时技术
5.多进程爬虫
- multiprocessing
- p = multiprocessing.Process(trage=func,args=(,))
- p.daemon = True #设置为守护进程,主线程结束,子进程结束
- p.start()
- from multiprocessing import JoinableQueue
- q = JoinableQueue()
- q.join() # 让主进程阻塞,等待队列任务计数,计数为0队列任务结束,
- q.put() # 计数+1
- q.get() # 计数不会-1
- q.task_done() # get和task_done一起使用才会减一
6.线程池和协程池的使用
- 线程池
- from multiprocessing.dummy import Pool
- pool = Pool(5)
- pool.apply_async(func,callback=func2)
- 协程池
- import gevent.monkey
- gevent.monkey.patch_all()
- from gevent.pool import Pool
- pool = Pool(5)
- pool.apply_async(func,callback=func2)
六、
1.安装driver
- chromdriver 需要对应chrome版本
- 提示权限不足,sudo chmod +x phantomjs
- chromdriver --version
- phantomjs --version
2.selenium如何使用
-
功能:请求页面,提取数据,开启隐形的浏览器,能够执行其中的js,可获取cookie
from selenium import webdriver
driver = webdriver.PhantomJS() # 没有界面,不建议使用
driver = webdriver.Chrome() # 带界面
driver.get_cookie(‘name’) # 获取cookie值,需要传name
driver.get_cookies() # 获取cookie
driver.get(url) #发送请求
driver.quit()
3.selenium定位元素的方法
- driver.find_element #返回第一个元素,如果没有报错
- driver.find_elements 返回包含元素的列表,没有就是空列表
- driver.find_elements_by_xpath() # 只能定位到标签(即元素), 不能定位到文本值和属性值
- driver.find_elements_class_name() #定位class属性
- driver.find_elements_by_id() #定位id属性
- driver.element.text #获取文本
- driver.element.get_attribute(“textContent”) #获取隐藏元素的文本
- driver.element.get_attribute(“href”) #元素获取属性值
4.selenium如何处理frame
- driver.switch_to.frame(id,name,element)
5.验证码的识别
- url地址不变,验证码不变
- url地址不变,验证码变化
- 请求验证码,发送登录请求,需要带上统一套cookie,才能够都能路成功,对应可以使用requests.Session()来实现
- selenium处理验证码
- 带上selenium的driver中的cookie来请求验证码
- selenium截屏,获取验证
6.mongodb的服务端和客户端启动方法
- 服务端启动
- sudo service mongod start
- sudo mongod --config /ect/mongod.conf &
- 客户端启动
7.mongodb中数据库的方法
- 数据库可以不需要提前创建,使用use一个不存在的数据库即可创建
- use db_name 使用数据库 数据库可以不存在
- db 查看当前所在的数据库
- show dbs /show databases 查看所有的数据库
- db.dropDatabase() 删除数据库
- 数据库名.dropDatabase() #删除数据库
8.mongodb中集合的方法
- 集合不需要提前创建,插入数据的时候自动创建
- show collections #查看所有的集合
- db.集合名.drop() #删除集合
- db.集合名.find() #集合的使用
9.mongodb的增删改查的方法
- 插入insert
- insert() 插入数据,_id相同会报错
- save() 保存数据_id相同会更新,不存在会插入
- 删除remove
- db.col_name.remove({条件},{justOne:flase}) #默认删除全部满足条件的内容
- db.col_name.remove({条件},{justOne:ture}) 删除一条满足条件的内容
- 更新update
- update 更改时,找到满足条件时,除id之外全部覆盖
- db.collection.update({条件},{$set:{name:10086}},{multi:true})
#默认更新一条,multi为true会更新全部
- db.col_name.update({条件},{name:1}) #会把满足条件的数据的第一条更新为{name:1}
- db.col_name.update({条件},{$set:{name:1}})
- db.col_name.update({条件},{$set:{name:1}},{multi:true})
七、
1.mongodb在pycharm中的增删改查
stu.insert({"name": "张三", "age": 12}) # 增
stu.remove({"age": 12}, multi=True) # 删
stu.update({"age": 12}, {"$set": {"name": "李斯"}}, multi=True) # 改
for data in stu.find(): # 查
print(data)
2.mongodb的运算符
- 比较运算符
- $gt 大于
- $lt 小于
- $gte 大于等于
- $lte 小于等于
- $ne 不等
- 逻辑运算符
- and {age:"",name:""}
- or {$or:[{条件1},{条件2}]}
- 范围运算符
- $in db.col.find({age:{$in:[18,19,30]}})
- $nin 不在范围内
3.mongodb中的计数,去重,排序
- 计数
- db.col.count({条件})
- db.col.find({条件}).count()
- 去重
- db.col.distinct("字段",{条件})
- 排序
- db.col.find().sort({})
- 投影
- 指定数据内容的字段
- db.stu.find({条件},{name:1,_id:0})
- 返回的数据中只会包含name字段,_id不会显示
4.mongodb聚合中$group的使用
-
分组
db.stu.aggregate(
{KaTeX parse error: Expected '}', got 'EOF' at end of input: group:{_id:"hometown",count:{KaTeX parse error: Expected 'EOF', got '}' at position 6: sum:1}̲,total_age:{sum:“KaTeX parse error: Expected 'EOF', got '}' at position 5: age"}̲,avg_age:{avg:”$age"}}}
)
-
_id分组的依据
-
$age 取age对应的值
-
$sum:1 把每条数据作为1进行统计,统计的是个数
-
s u m : " sum:" sum:"age" 统计年龄对应的和
-
$group对应的字典中的键是输出数据的键
-
不分组
db.stu.aggregate(
{KaTeX parse error: Expected '}', got 'EOF' at end of input: …id:null,count:{sum:1}}}
)
按照一个字段分组
db.col.aggregate(
{KaTeX parse error: Expected '}', got 'EOF' at end of input: group:{_id:"gender",count:{KaTeX parse error: Expected 'EOF', got '}' at position 6: sum:1}̲}} ) 按照多个…group:{_id:{gender:“ g e n d e r " , h o m e t o w n : " gender",hometown:" gender",hometown:"hometown”},count:{KaTeX parse error: Expected 'EOF', got '}' at position 6: sum:1}̲}} ) 不分组,…group:{_id:null,count:{$sum:1}}}
)
KaTeX parse error: Expected '}', got 'EOF' at end of input: …ggregate( {group:{_id:“KaTeX parse error: Expected '}', got 'EOF' at end of input: gender",name:{push:”$name"}}}
)
4.统计整个文档
-
数据透视
把不同行的数据,放到一行来展示
db.stu.aggregate(
{KaTeX parse error: Expected '}', got 'EOF' at end of input: group:{_id:"gender",name:{ p u s h : " push:" push:"name"},hometown:{ p u s h : " push:" push:"hometown"}}}
)
-
按照多个字段进行分组
按照多个字端进行分组,_id的值是一个json
db.stu.aggregate(
{KaTeX parse error: Expected '}', got 'EOF' at end of input: …_id:{hometown:"hometown",gender:"KaTeX parse error: Expected 'EOF', got '}' at position 8: gender"}̲,count:{sum:1}}}
)
-
多字段分组练习
当某个键对应的值是字典的时候,取其中的值需要使用.操作,$_id.country表示取到_id这个字典下的country的键对应的值
-
第一条作为第二条的管道进行查找,计数
db.tv3.aggregate(
{KaTeX parse error: Expected '}', got 'EOF' at end of input: …{_id:{country:"country",province:“ p r o v i n c e " , u s e r i d : " province",userid:" province",userid:"userid”}}},
{KaTeX parse error: Expected '}', got 'EOF' at end of input: …{_id:{country:"_id.country",province:"KaTeX parse error: Expected 'EOF', got '}' at position 14: _id.province"}̲,count:{sum:1}}}
)
5.mongodb中$match
过滤
db.col.aggregate(
{$match:{age:{$gt:18}}}
)
6.mongodb中$project
投影,修改文档的输入输出结构
db.stu.aggregate(
{$group:{_id:"$hometown",count:{$sum:1}}},
{$project:{_id:0,sum:"$count",hometown:"$_id"}}
)
db.tv3.aggregate(
{$group:{_id:{country:"$country",province:"$province",userid:"$userid"}}},
{$group:{_id:{country:"$_id.country",province:"$_id.province"},count:{$sum:1}}},
{$project:{country:"$_id.country",province:"$_id.province",counter:"$count",_id:0}}
)
7.limit $sort
db.stu.aggregate(
{$group:{_id:"$hometown",count:{$sum:1}}},
{$sort:{count:-1}},
{$skip:1},
{$limit:2}
)
8.mongodb索引
- 创建索引
- db.col.ensureIndex({name:1})
- db.col.createIndex()
- 查看索引
- 删除索引
- db.col.dropIndex({name:1})
- 建立联合索引
- db.col.ensureIndex({name:1,age:-1})
- 建立唯一索引
- db.col.ensureIndex({name:1},{unique:true})
9.mongodb备份和恢复
- 备份
- mongodump -h host -d database -o output_path
- 恢复
- mongorestore -h host -d database --dir 恢复的路径
10.pymongo的使用
from pymongo import MongoClient
#实例化client
client = MongoClient(host,port)
#选择集合
collection = client["db"]["collection"]
#查询
collection.find() #返回全部的数据,返回cursor对象,只能获取其中内容一次
collection.find_one() #返回一条
#插入
collection.isnert_one()
collection.insert_many()
#更新
collection.update_one({name:"a"},{"$set":{"name":"noob"}})
collection.update_many()
#删除
collection.delete_one()
collection.delete_many()
八、
1.scrapy框架安装
- pip install Twirted.whl(本地)
- pip install pywin32
- pip install scrapy
2.scrapy 的数据传递的流程
- 五大组件
- 调度器:存储请求队列
- 下载器:根据request发情请求获得响应response
- 爬虫:提取url转为request,提取数据
- 管道:数据清洗和数据保存
- 引擎:负责连接其他四个组件(之间互不相通),保证数据的传递
- 中间件 只能处理request和response
- 运行过程
0.调用start_requests()方法,将start_urls中所有的url构造成request对象,并放入调度器
1.调度器取一个request -> 引擎 -> 下载器中间件 -> 下载器
2.下载器根据request下载得到response -> 下载器中间件 -> 引擎 -> 爬虫中间件 -> 爬虫
3.爬虫提取数据
3.1 爬虫提取url转为request -> 爬虫中间件 -> 引擎 -> 调度器
3.2 爬虫提取数据(item) -> 引擎 -> 管道
4.管道实现数据的处理和保存
3.scrapy爬虫项目的创建
- 创建项目
scrapy startproject myspider
- 创建爬虫
cd myspider
scrapy genspider spider_name allowed_domain
- 运行
scrapy crawl spider_name
- 构造一个request请求
yield scrapy.Request(url,callback,meta,dont_filter) # url需要手动补全
callback: 将来url响应的处理函数
meta:数据不完整时,传递当前数据到下一个响应函数
dont_filter:默认False,即过滤,过滤的情况下,不会重复发起相同的url请求
response.follow(url) # url不用补全,会根据response.url自动补全
- 完善爬虫 :提取数据,提取url地址组成request
- 完善管道:数据的处理和保存
- yield 能够yield None,item对象,字典,或者请求,不能够yield一个列表
4.完善spider
- parse方法必须有,用来处理start_urls对应的响应的
- extract() response.xpath() 从中提取数据的方法,没有就返回一个空列表
- extract_first() response.xpath() 的结果中提取第一个字符串的方法,没有返回None值
5.完善管道
-
管道需要在settings中开启,添加管道的路径,对应的键:管道的位置,值表示的是管道距离引擎的远近,数字越小,优先级越高,越先经过
-
process_item(item,spider)方法必须有 ,spider表示的是传递item过来的爬虫实例
-
从爬虫中通过yield 把数据交给引擎传递给pipeline,只能是Request, BaseItem, dict or None
class YangguangPipeline(object):
def process_item(self, item, spider):
处理从spider发来的item数据
需要在配置文件中开启后才能生效
ITEM_PIPELINES = {
‘yangguang.pipelines.YangguangPipeline’: 300, # 300标识数据处理的优先级,数字越低,优先级越高}
6.数据提取url地址补全
- 手动字符串相加
- urllib.parse.urljoin(baseurl,url)
- 后面的url会根据baseurl进行url地址的拼接
- import urllib
- url1 = “position.php?&start=2890#a”
- url2 = “https://hr.tencent.com/position.php?&start=3580#a”
- urllib.parse.urljoin(url2, url1)
- 输出:“https://hr.tencent.com/position.php?&start=2890#a”
- next_url = urllib.parse.urljoin(response.url, next_url) # response.url可以获取完整的url字符串,是response的一个属性
- response.follow(url,callback)
- 能够根据response的地址把url拼接完整,构造成Request对象请求
7.scrapy如何构造请求
- scrapy.Request(url,callback,meta,dont_filter)
- url:详情页,下一页的url
- callback:url地址响应的处理函数
- meta:在不同的函数中传递数据
- dont_filter ::默认是false表示过滤,scrapy请求过的url地址,在当前的运行过程中不会继续被请求,如果需要继续被请求,可以把dont_filter=True
- yield scrapy.Request(url,callback,meta,dont_filter)
8.scrapy的Item如何使用
#定义
class Item(scrapy.Item):
name = scrapy.Field()
#使用
导入。使用name字典
9.scrapy中parse函数是做什么的
九、
1.scrapy shell 如何使用,能干什么
- scrapy shell url 能够进入交互式终端
- 查看scrapy中模块的属性
- 测试xpath
2.response对象有哪些常见属性
- response.body 能够后去响应bytes字符串
- response.url
- response.request.url
- response.headers
- response.request.headers
- resposne.text 能够后去响应str字符串
3.open_spider 和close_spider 在管道里面配置
- open_spider(spider) #能够在爬虫开启的时候执行一次
- close_spdier(spider) #能够在爬虫关闭的时候执行一次
- 在和数据库建立连接和断开连接的时候使用上述方法
4.deepcopy的使用,苏宁代码中为什么需要deepcopy
- a = deepcopy(b) #强制传值
- 苏宁代码数据重复
- scrapy中的内容是异步执行的,解析函数可能同时在执行,操作的同一个item,
- 大分类下的所有的图书用的是一个item字典
十、
1.crwalspider如何创建爬虫
- scrapy genspider -t crawl 爬虫名 语序允许爬取的范围
2.crwalspdier中rules的编写
- rules 元组,元素是Rule
- Rule(LinkExtractor(allow=“正则”),follow=True,callback=“str”)
- LinkExtractor:传入正则匹配url地址
- follow:为True表示提取出来的响应还会经过rules中的规则进行url地址的提取
- callback:表示提取出来的响应还会经过callback处理
3.crwalspider中不同的解析函数间如何传递数据,如果不能,应该如何操作?
-
在前一个Rule的callback中实现手动构造请求
yield scrapy.Request(url,callback,meta)
4.下载器中间件如何使用
使用代理
-
request.meta[‘proxy’] = “协议+ip+端口”
-
在settings中开启中间件
SPIDER_MIDDLEWARES = {
‘book.middlewares.BookSpiderMiddleware’: 543,
}
DOWNLOADER_MIDDLEWARES = {
‘book.middlewares.BookDownloaderMiddleware’: 543,
}
class TestMid:
def process_request(self,request,spdier):
#处理请求
request.headers[“User-Agent”] = “” #使用ua
request.meta[“proxy”] = “协议+ip+端口”
return None #请求会继续后面处理
return Request #把请求交给调度器
return Response #把响应交给爬虫
def process_response(self,request,response,spider):
#处理响应
return response #把响应交给爬虫
return request #把请求交给调度器
5.模拟登陆的三种方式
- 携带cookie进行登录
- yield scrapy.Request(url,callback=self.parse,cookies={},meta={})
- 发送post请求
- yield scrapy.FormReuqest(url,formdata={请求体},callback=self.parse_login)
- 表单提交
- yield scrapy.FormReuqest.from_response(response,formdata={},callback=self.parse_login)
6.正则比倒是忽略大小写
十一、
- scrapy_redis
scrapy_redis 是scrapy框架的一个扩展组件,实现了两个功能:
实质:就是将请求队列和指纹集合进行了持久化存储
在seeeting.py中继续配置
# 指定了去重的类
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 制定了调度器的类
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 调度器的内容是否持久化
SCHEDULER_PERSIST = True
REDIS_URL = "redis://127.0.0.1:6379"
注:scrapy_redis 只是scrapy框架的一个组件
2.scrapy_redis为什么能够实现去重和请求的持久化以及分布式爬虫
- scrapy_redis把指纹和request对象存储在了redis,下一次程序启动起来之后会从之前的redis中读取数据
- 实现分布式:过个服务器上的爬虫公用一个redis
3.scray_redis中dmoz给我们展示了一个什么样的爬虫
- 增量式的爬虫:基于request对象的增量
- 分布式爬虫
- 主要体现在 请求队列和指纹集合的持久化存储
4.scrapy_redis中如何实现一个增量式的爬虫
- settings中进行配置,指定去重的类,调度器的类,添加上redis_url
DUPEFILTER_CLASS = “scrapy_redis.dupefilter.RFPDupeFilter” # 指定去重的类
SCHEDULER = “scrapy_redis.scheduler.Scheduler” # 调度器的类
SCHEDULER_PERSIST = True # 指定调度器的内容是否持久化
REDIS_URL = “redis://127.0.0.1:6379” # 添加redis_url地址
5.scrapy中如何生成的指纹,有什么启发
使用sha1加密请求的url地址,请求的方法和请求体,得到16进制字符串作为指纹 每个指纹40位
fp = hashlib.sha1() # sha1加密
fp.update(to_bytes(request.method)) # 请求方法
fp.update(to_bytes(canonicalize_url(request.url))) # 请求地址
fp.update(request.body or b'') # 请求体
cache[include_headers] = fp.hexdigest() #生成指纹 加密之后的16进制字符串
6.scrapy_redis什么情况下request会入队
def enqueue_request(self, request):
if not request.dont_filter and self.df.request_seen(request):
self.df.log(request, self.spider)
return False
self.queue.push(request)
return True
- 全新的request对象,之前没有见过的request
- dont_filter = True 不过滤,请求过的url地址让他继续请求
- start_urls中的url地址能够反复入队请求,因为默认是不过滤的
7.要查看数据是否存在于redis的集合中,如果不存在就插入
added = self.server.sadd(self.key, fp)
#added= 0 表示存在
#added!=0 表示不存在,并且已经插入
return added == 0
8.关于对象的序列化和反序列化
request = Request("http://www.baidu.com", meta={"item": {"hello": "wrold"}}, parse="parse_book")
- 对象的序列化
data = pickle.dumps(request)
print(data)
- 反序列化
req = pickle.loads(data)
print(req.meta) print(id(request), id(req))
- 如何去重
1.请求生成指纹
fp = hashlib.sha1()
fp.update(to_bytes(request.method))
fp.update(to_bytes(canonicalize_url(request.url)))
fp.update(request.body or b'')
return fp.hexdigest()
利用hashlib的sha1,对request的请求体、请求url、请求方法进行加密,返回一个40位长度的16进制的字符串,称为指纹
-
进队
def enqueue_request(self, request):
if not request.dont_filter and self.df.request_seen(request):
self.df.log(request, self.spider)
return False
self.queue.push(request)
return True
-
如果请求需要过滤,并且当前请求的指纹已经在指纹集合中存在了,就不能进入队列了
-
如果不需要过滤,直接进入队列
-
如果请求需要过滤,并且请求的指纹是一个新的指纹,进入队列
10.数据去重
- 中间件去重
process_response(request,response,spider):
#set可以是内存set集合,也可以是redis的set
ret = set.add(md5(response.body))
if ret == 0:
return request
else
return response
- 建立复合索引
# 复合索引,加速和去重
stu.ensure_index([(“hometown”, 1), (“age”, 1)], unique=True)
# 根据数据的特征,在mongodb中 对指定字段建立复合索引,所有字段值相同时就无法二次插入了
- 布隆过滤器
11.哈希函数的特性:
- 输入域无限,输出域有限
- 相同的输入 必然 得到 相同的输出 (不是随机性)
- 不同的输入 也可能 得到 相同的输出 (哈希碰撞)
- 离散性,对于输出域中的每个结果,在整个输出域是均分分布的。
- dict 字典 -> 哈希表
十二、
1.Redisspider的爬虫和scrapy.spider的区别
实现Redisspider 分布式爬虫,请求的持久化,去重的持久化
- 区别
- 父类不一样,RedisSpider继承的父类是RedisSpider
- RedisSpider没有start_url, 多了redis_key ,往redis_key存入start_url地址
- redis_key表示redis中存放start_url地址的键
- settings 中多了几行配置
- 创建爬虫
- scrapy genspider 爬虫名 爬取范围
- 修改父类名
- 修改redis_key
- 启动爬虫
- 让爬虫就绪:scrapy crawl 爬虫名
- redis中存入url地址:lpush redis_key url
2.RedisCrawlSpider的爬虫和crwalspdier的区别
实现RedisCrawlSpider 分布式爬虫,请求的持久化,去重的持久化
- 区别
- RedisCrawlSpider继承的父类是RedisCrawlSpider
- RedisCrawlSpider没有start_url,多了redis_key ,往redis_key存入start_url地址
- redis_key表示redis中存放start_url地址的键
- settings 中多了几行配置
- 创建爬虫
- scrapy genspider -t crawl 爬虫名 允许爬取的范围
- 修改父类名
- 添加redis_key
- 启动爬虫
- 让爬虫就绪 scrapy crawl 爬虫
- lpush redis_key url 爬虫会启动
3.crontab使用的方法
- 分钟 小时 日 月 星期 命令
- 30 9 8 * * ls #每个月的8号的9:30执行ls命令
在爬虫中使用crontab
-
-
爬虫启动命令写入脚本文件
cd dirname $0
scrapy crawl 爬虫名 >> run.log 2>&1
-
- 给脚本添加可执行权限
-
- 把脚本文件添加到crontab的配置中
- 30 6 * * * /home/python/myspider/run.sh
十三、
1.什么是框架,为什么需要开发框架
- 框架:为了解决一类问题而开发的程序,能够提高开发效率
- 第三方的框架不能够满足需求,在特定场景下使用,能够满足特定需求
2.scrapy_plus中有哪些内置对象和核心模块
- core
- engine
- scheduler
- downloader
- pipeline
- spider
- http
- middlewares
- downloader_middlewares
- spider_middlewares
- item
3.说出scrapy_plus实现引擎的基础逻辑
- 1.调用爬虫的start_request方法,获取start_request请求对象
- 调用爬虫中间件的process_request方法,传入start-request,返回start_request
- 2.调用调度器的add_request,传入start_request
- 3.调用调度器的get_request方法,获取请求
- 调用下载器中间件的process_request,传入请求,返回请求
- 4.调用下载器的get_response方法,传入请求,返回response
- 调用下载器中间件的process_response方法,传入response,返回response
- 调用爬虫中间件的process_response方法,传入response,返回response
- 5.调用spider的parse方法,传入resposne,得到结果
- 调用爬虫中间件的process_request方法,传入request,返回request
- 6.判断结果的类型,如果是请求对象,调用调度器的add_request,传入请求对象
- 7.否则调用管道的process_item方法,传入结果
4.如何在项目文件中添加配置文件能够覆盖父类的默认配置
- 1.在框架中conf文件夹下,建立default_settings,设置默认配置
- 2.在框架的conf文件夹下,建立settings文件,导入default_settings中的配置
- 3.在项目的文件夹下,创建settings文件,设置用户配置
- 4.在框架的conf文件夹下的settings中,导入settings中的配置,会覆盖框架中的默认配置
十四、
1.getattr如何使用?
- getattr 通过传入字符串,获取python对象或者是方法
- 现在有test(),知道test中间有个方法名叫做func
- getattr(test(),“func”) #返回test.func()
2.importlib如何使用?
- 能够动态的导入模块
import importlib
module = importlib.import_module(“模块的位置”)
cls = getattr(module,“Test”) #获取模块下的类
func = getattr(cls(),“func”) #获取cls中的func方法
爬虫项目
项目
- 运行环境
- linux+pycharm+redis+scrapy+mysql+mongodb+scrapy_redsi+selenium
- 项目描述
- 抓取了(多个)网站,获取了数据,解决了***需求,使用的技能
- request+selenium
- selenium + scrapy
- scrapy_redsi
- 自己实现的框架完成了一个项目
- 个人职责
- 使用的是技能
- request :发送请求
- selenium:
- 获取动态html页面,数据提取更方便
- 专门进行登录,获取cookie,组成cookie池,其他程序从cookie池中获取cookie,请求登陆之后的页
- scrapy:
- scrapy_redis
- 为了试下增量式爬虫,使用了scrapy_redis
- 为了实现分布式,使用了scrapy_redis
- 为了实现持久化的去重,
- 自己实现的框架完成了一个项目
- 框架的实现逻辑
- 实现去重的方式
- 分布式的实现方式
- 持久化的实现
- 反扒
- js生成的数据
- 使用selenium配合无头浏览器
- 分析了js,看到了js的实现过程,python实现了一遍
- js2py的工具执行了js
- 验证码
- 代理ip
- 对方服务器有通过ip进行限速,购买了代理ip组成了ip池,通过一个程序,判断ip的可用性
- 去重
- 基于url地址去重
- 基于数据的去重
- sha1加密了数据中的某些字典,得到指纹,存在redis中的集合中进行对数据的去重
- 在数据库中建立联合索引进行去重