编写程序,模拟游览器上网,然后让其去互联网上抓取数据的过程
爬虫在使用场景的分类
- 通用爬虫
爬取系统重要组成部分,抓取的是一整张页面的数据 - 聚焦爬虫
建立在通用爬虫的基础上,抓取的是页面中特定的局部内容 - 增量式爬虫
检测网站中数据更新的情况,只会抓取网站中最新更新出来的数据
robots.txt协议
文件中规定了哪些数据可以爬取和不可以爬取
网址/robots.txt
http:
服务器和客户端进行数据交互的一种形式
请求头信息:
- user-Agent:请求载体的身份标识(游览器版本以及终端版本)
- Connection:请求完毕后是断开连接还是保持连接。
响应头信息:
- conntent-type:服务器响应客户端的数据类型
https:
安全的超文本传输协议
加密方式:
- 对称密钥加密
- 非对称密钥加密
- 证书密钥加密
requests模块
requests模块:python原生的一款基于网络请求的模块,模拟游览器发请求
使用方法:
- 指定url
- 发起请求,post和get请求
- 获取响应体对象中的数据
- 持久化存储
import reqtests
url = "https://www.sogou.com"
#指定url
response = requests.get(url=url)
# get 方法会返回一个响应对象
response.encoding ='utf-8'
#设置响应数据的编码格式,爬取的数据有乱码的时候可以设置
response_text = response.text
#text获取的是字符串形式的响应数据
response_text = response.json()
#json()获取的是json对象的响应数据
response_text = response.content
#content获取的是二进制形式的响应数据
with open("c:/sougou.html",'w',encoding="utf-8") as file:
file.write(response_text)
import reqtests
url = "https://www.sogou.com"
headers={
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36"
} #伪装信息
kw = input("word")
param = {
'query':kw
}
response = requests.get(url=url,params=param,headers=headers)
# get方法会返回一个响应对象,params表示这是个有参数的url,并且参数在param字典中,headers是伪装请求头信息
response_text = response.text
#user-agent检测:门户网站会检测对应请求的载体的身份标识,如果监测到请求的载体身份标识为某一款游览器,说明这是一个正常的请求但是如果检测到的请求的载体身份标识不是基于某一款游览器的,则标识这个请求为不正常的请求,则服务器就很有可能拒绝该次请求、
#user-agent伪装:让爬虫对应的请求载体身份标识伪装成某一款游览器
# 百度翻译案例
import request
import json
post_url = "https://fanyi.baidu.com/sug"
headers={
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36"
}
data = {"kw":"dog"} #post请求携带的数据
response = request.post(url=post_url,data=data,headers=headers)
dict_obj = response.json()
# json()方法返回的是一个obj对象字典形式的(只有确认响应数据是json格式的才可以使用json()方法)
file = open("c:/dog.json","w",encoding="utf-8")
json.dump(dict_obj,fp = file,ensure_ascii=False)
数据解析
从整个页面里面解析需要的某部分数据
1.标签定位
2.提取标签,标签属性中存储的数据中
正则解析
bs4解析
需要安装 bs4 和 lxml模块
1.实例化一个BeautifulSoup对象,并且将页面源码数据加载到该对象中
2.通过调用BeautifulSoup对象中相关的属性或者方法进行标签定位和数据提取
from bs4 import BeautifulSoup
file = open("baidu.html",'r',encoding='utf-8')
soup = BeautifulSoup(file,'lxml')
# 将本地文件实例化为soup对象
file = response.text
soup = BeautifulSoup(file,'lxml')
# 将爬取的数据实例化为soup对象
soup.tagName
#获取soup对象中某个标签中包含的数据,返回的是文档中第一次出现的tagName
#包含的数据,tagname就是标签名
soup.find('div')
#用法等同于soup.tagName
soup.find('div',class_='song')
# 查找标签名叫div 且 classs属性是song的标签包含的数据,class_标识属性
soup.find_all('a')
# 查找所有的a标签包含的数据,是一个列表,也可以跟class_进行过滤
soup.select('.tang')
#查找选择器名称为tang的标签包含的数据,可以是标签选择器,类选择器,id选择器,是一个列表形式
soup.select('.tang > ul > li > a')
#查找id为tang 下的 ul标签下的 li 标签下的 a 标签包含的数据,是一个列表 >标识一个层级 空格标识多个层级
soup.a.text
#获取a标签中所有的文本内容,即使该内容不属于该标签的直系内容
soup.a.string
#获取a标签中直系的标签内容
soup.a.get_text()
#获取a标签中所有的文本内容,即使该内容不属于该标签的直系内容
soup.a['href']
#获取a标签中的href属性值
xpath解析
安装lxml模块,是一个解析器
1,实例化一个etree的对象,并且需要将被解析的页面源码数据加载到该对象
2.调用etree对象中的xpath方法结合xpath表达式实现标签的定位和内容的捕获
from lxml import etree
etree.parse('gh.html')
#将本地html文档中的源码数据加载到etree对象中,得到一个etree的对象
tree = etree.HTML(response.text)
#将爬取的源码数据加载到etree对象中,得到一个etree的对象
tree.xpath('/html/head/title')
#查找html标签下的 head下的 title标签中的内容,是一个对象放在列表中
#这个对象还是支持xpath方法
#第一个/表示从根节点开始定位,其他/表示是一个层级,.表示当前定位到的标签
tree.xpath('/html//title')
# //表示是多个层级
tree.xpath('//div[@class='song']')
# 查找所有的div且 class属性等于song的
tree.xpath('//div[@class='song']/p[3]')
# 查找所有的div且 class属性等于song的,下的第3个p标签,所以是从1开始的
tree.xpath('//div[@class='song']//li[5]/a/text()')
#查找所有的div且 class属性等于song的下的第5个li标下的a标签中的文本值
#得到的是一个列表,里面存放值
tree.xpath('//li[7]//text()')
#查找第7个li标签下的所有文本值,//可以跨多个层级取数据,/只能是一个层级
tree.xpath('//div[@class='song']/img/@src')
#查找所有的div且 class属性等于song的下的img标签的src属性值
验证码识别
- 超级鹰
模拟登陆cookie操作
cookie用来让服务端记录客户端的相关状态
- 手动处理:通过抓包工具获取cookie值,将该值封装到headers中
- 自动处理:cookie值的来源是,模拟登陆post请求后,由服务端创建返回给客户端的
- session对象
- 可以进行请求的发送
- 如果请求过程中产生了cookie,则该cookie会被自动存储/携带在该session对象中
- session对象的创建:request.session() 会返回一个session对象
- session对象
代理IP
门户网站一般会设置同一个ip的请求次数超过阈值后直接阻断该ip的连接,此时可以使用代理ip来进行访问
代理服务器
破解ip限制的作用
隐藏真实的ip
代理相关的网站
块代理
西斯代理
www.guobanjia.com
request.get(url=url,proxies={"https":"127.9.8.8:808"})
https网站使用https
http网站使用http
代理IP的匿名度:
- 透明 :服务器知道该次请求使用了代理地址,也知道请求对应的真是ip
- 匿名 :服务器知道使用了代理,但是不知道真实ip
- 高匿:不知道使用了代理,也不知道真实的ip
高性能异步爬虫
import asyncio
async def func1():
print(1) #输出1
await asyncio.sleep(2)#io等待2s 在内部自动切换到任务2 2秒等待完成了线程自动切换回来往下执行,#不能使用time.sleep(),这样的话是同步,就不是异步;await就相当于yield from
print(2)
async def func2():
print(3)
await asyncio.sleep(2)
print(4)
# 创建两个任务, tasks就是一个任务列表
tasks = [
asyncio.ensure_future(func1()),
asyncio.ensure_future(func2()),
]
#在内部基于io loop 开始执行两个任务 内部让一个进程中的一个线程执行
loop = asyncio.get_event_loop()
loop.run_untill_complete(asyncio.wait(tasks))#将tasks放到loop中,进行事件循环, 这里必须传入的是一个list,任务列表只能放到asyncio.wait()中
---------------------------------------------------------------
event_loop:事件循环,相当于一个无限循环,可以把一些函数注册到这个无限
循环上,当满足某些条件的时候,函数就会被循环执行
coroutine:协程对象,可以将协程对象注册到事件循环中,他会被事件循环调用
可以使用async 关键字来定义一个方法,这个方法在调用时不会立即执行,而是
返回一个协程对象
task:任务对象,是对协程对象的进一步封装,包含了任务的各个状态
future:代表将来执行或者还没有执行的任务,实际上和task没有本质区别
协程中一旦出现同步的代码,就无法实现异步效果
------------------------------------------------------------------
async:定义一个协程,async修饰的函数,调用该函数之后返回一个协程对象
loop = asyncio.get_event_loop():创建一个事件循环对象
loop.run_untill_complete(协程对象):将协程对象注册到事件循环对象中,然后启动循环对象,该协程对象(也就是函数中的语句就会开始执行)就会开始执行。协程对象或任务对象注册到事件循环对象中就会执行协程对象或任务对象, # 把一些异步函数注册到这个事件循环上,事件循环会循环执行这些函数,当执行到某个函数时,如果它正在等待I/O返回,如它正在进行网络请求,或者sleep操作,事件循环会暂停它的执行去执行其他的函数;当某个函数完成I/O后会恢复,下次循环到它的时候继续执行。因此,这些异步函数可以协同(Cooperative)运行:这就是事件循环的目标
task = loop.create_task(协程对象):基于loop(事件循环对象)创建一个task任务对象,task任务对象也可以注册到事件循环对象中
task = asyncio.ensure_future(协程对象):基于future的方式注册一个任务对象,该对象也可以被注册到事件循环对象中
task.add_done_callback(回调函数名);将回调函数(任务执行完成后会自动调用这个函数)绑定到任务对象中。会将任务对象自动传参给回调函数的参数,参数.result,返回的就是任务对象中封装的协程对象对应函数的返回值
await:用来挂起阻塞方法的执行,用来用来声明程序挂起,在asyncio中遇到阻塞操作必须进行手动挂起#比如异步程序执行到某一步时需要等待的时间很长,就将此挂起,去执行其他的异步程序。await 后面只能跟异步程序或有__await__属性的对象,因为异步程序与一般程序不同。假设有两个异步函数async a,async b,a中的某一步有await,当程序碰到关键字await b()后,异步程序挂起后去执行另一个异步b程序,就是从函数内部跳出去执行其他函数,当挂起条件消失后,不管b是否执行完,要马上从b程序中跳出来,回到原程序执行原来的操作。如果await后面跟的b函数不是异步函数,那么操作就只能等b执行完再返回,无法在b执行的过程中返回。如果要在b执行完才返回,也就不需要用await关键字了,直接调用b函数就行。所以这就需要await后面跟的是异步函数了。在一个异步函数中,可以不止一次挂起,也就是可以用多个await。
requests.get()是基于同步的
aiohttp:是基于异步网络请求
async with aiohttp.ClientSession as session:#创建一个session对象
async with await session.get(url) as response:
page_text = await response.text()
# 在获取响应数据操作之前一定要使用await进行手动挂起
text()返回的是字符串形式的响应数据
read()返回的是二进制形式的响应数据
json()就是json的
selenium
基于游览器自动化的一个模块,可以便捷的获取网站中动态加载的数据,便捷实现模拟登陆。
使用流程
- 安装模块 pip install selenium
- 下载游览器驱动程序 注意版本关系
- 实例化游览器对象
- 编写基于游览器自动化的代码
from selenium import webdriver
web_obj = webdriver.Chrome(executable_path="游览器驱动路径")
#实例化游览器对象
web_obj.get('网址')
#让游览器向网址发起get请求
web_obj.save_screenshot('./123.png')
#对打开的页面进行全局截图,并并保存
page_text = web_obj.page_source
#获取游览器当前页面的页面源码数据
search_input =web_obj.find_element_by_id('qw')
#根据id定位文本输入框
search_input.send_keys('手机')
#标签交互,向输入框输入手机
btn=web_obj.find_element_by_css_selector('.btn-search')
#根据css选择器定位搜索按钮
web_obj.execute_script('windows.scrollto(0,document.body.scrollHeight)')
#执行js代码,实现滚动条滚动一屏幕的高度
web_obj.back()
#游览器回退前一页面
web_obj.forword()
#游览器返回上一页面
btn.click()
#点击搜索按钮
web_obj.quit()
#退出游览器
image=web_obj.find_element_by_css_selector('.image')
#定位图片
location= image.location
# 获取图片左上角的坐标x,y ,字典类型
size = image.size
#获取图片的长和宽,字典类型
range =(int(location['X']),int(location['y']),int(location['x']+size['width']),int(location['y']+size['weight']))
#计算图片的左上角和右下角的坐标
from PIL import Image
#裁剪图片的类
i= Image.open('./123.png')
# 实例化一个图片对象,就是截图的那个全局图片
code_img_name = 'code.png'
frame = i.crop(range)
#根据坐标对图片进行裁剪
frame.save(code_img_name)
#对裁剪的图片进行保存
selenium处理iframe
iframe在当前页面嵌套一个子页面
如果定位的标签是存在里iframe标签里的要先调用switch_to.frame(‘id’)
from selenium import webdriver
from selenium.webdriver import ActionChains
#导入动作链的类
web_obj = webdriver.Chrome(executable_path="游览器驱动路径")
web_obj.switch_to.frame('iframe标签的id值')
#切换游览器标签定位的作用域为iframe
动作链
from selenium import webdriver
from selenium.webdriver import ActionChains
#导入动作链的类
action = ActionChains(web_obj)
#实例化一个动作链
#动作链就是一个移动的操作,比如拖动某个元素
action.click_and_hols(定位到的标签)
#点击并长按定位到的标签
action.move_by_offset(17,0).perform()
# 让定位到的标签水平移动17像素,立即执行
#action.move_by_offset(x,y).perform()
action.release()
#释放动作链
image=web_obj.find_element_by_css_selector('.image')
#定位图
x = 10
y = 20
action.move_to_element_with_offset(image,x,y).click().perform()
#将动作链的参照物切换为iamge 图片,当鼠标移动到x,y坐标的时候进行点击,立即执行
selenium模拟登陆
游览器无可视化界面(无头游览器)
from selenium import webdriver
from selenium.webdriver.chorm.options import Options#实现无可视化的类
chrome_options =Options() # 实例化对象,用来控制游览器以无界面的方式打开
chrome_options.add_argument("--headless")
chrome_options.add_argument("--disable-gpu")
web_obj = webdriver.Chrome(executable_path="游览器驱动路径",chrome_options=chrome_options)
web_obj.get('网址')
selenium规避被检测识别
大部分网站对selenium采取了检测机制,比如正常情况下使用游览器访问网站的windows.navigator.webdriver的值为undefined 而使用selenium访问则该值为True.
在启动Chromedriver之前为Chrome开启实验性功能参数excludeSwitches
值为['enable-automation']
from selenium.webdriver import Chrome
from selenium import webdriver
from selenium.webdriver import ChromeOptions#规避检测的类
option = ChromeOptions()
option.add_experimental_option('excludeSwitches',['enable-automation'])
web_obj = webdriver.Chrome(executable_path="游览器驱动路径",options=option)#将设置的参数,传递给实例化的游览器
scrapy框架
框架就是集成了很多功能并且具有很强通用性的一个项目模板
scrapy提供了高性能的持久化存储,异步的数据下载,高性能的数据解析,分布式
环境安装
Linux:pip install scrapy
windows:
- pip install wheel #用于安装whl文件
- 下载twisted 下载地址为http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted #异步功能是基于此模块实现的,文件名中的cp对应的是python版本
- 安装twisted: pip install 下载的twisted文件名称
- pip install pywin32
- pip install scrapy
- 终端下测试是否安装成功 终端输入scrapy 无报错即为成功
基本使用
创建工程:scrapy startproject XXX
创建后的文件目录
XXX文件夹
——-XXX文件夹
—————spiders文件夹,爬虫目录,存放爬虫源文件
————— items.py
数据持久化,封装数据对象的
————— middlewares.py
—————pinelines.py管道文件:
class 项目名称PipeLine(object):
#专门用于处理item类型对象
def process_item(self,item,spider):
#该方法可以接收爬虫文件提交过来的item对象
#该方法每接收一个item就会被调用一次
return item
————— settings.py
——- scrapy.cfg ,配置文件在 spiders文件夹中创建 爬虫文件:
scrapy genspider 名称 www.baidu.com(起始url) ```python import scrapy
class FirstSpider(scrapy.Spider): name = ‘first’
#爬虫文件的名称,终端创建文件的时候指定的,爬虫源文件的唯一标识,多个爬虫源文件不可以重名
allowed_domains = ['www.baidu.com']
#允许的域名,用来限制start_urls中的哪些url可以进行请求发送,注释掉就是所有的都可以访问
start_urls = ['http://www.baidu.com',]
#起始的url列表,列表中存放的url会被scrapy自动进行请求的发送
def parse(self,response):
#写数据解析相关的操作,response表示的是start_urls自动发送请求后的响应对象,start_url中有几个url就调用几次parse方法,用于接收响应对象
pass
- 在爬虫文件中写代码
- 执行工程: scrapy crawl 名称 ,scrapy crawl 名称 --nolog 执行过程不输出日志
<a name="79c706da"></a>
## scrapy数据解析
```python
import scrapy
class FirstSpider(scrapy.Spider):
name = 'first'
allowed_domains = ['www.baidu.com']
start_urls = ['http://www.baidu.com',]
def parse(self,response):
div_list = response.xpath("//div[@id="content"]/div")
#xpath返回的是一个列表,列表里的元素是一个selector类型的对象
#取数据的时候用.extract(),可以将selector对象中的data参数 #存储的字符串取出来
#如果是列表调用的extract()方法,表示将列表中的每一个 #selector对象中的data对应的字符串取出来
scrapy持久化存储
- 基于终端指令:
只能将parse方法中的返回值存储到本地文本文件中终端命令执行:
scrapy crawl 爬虫文件名称 -o ./123.csv
将爬虫文件中的 parse方法的返回值保存到123.csv中
只支持保存到json jsonlines jl csv xml marshal pickle文件中
- 基于管道:
import scrapy import 项目名称.items import 项目名称Item
引入item类
class FirstSpider(scrapy.Spider): name = ‘first’ allowed_domains = [‘www.baidu.com’] start_urls = [‘http://www.baidu.com‘,]
def parse(self,response):
div_list = response.xpath("//div[@id="content"]/div")
for div in div_list:
author = div.xpath("./div[1]/a[2]/h2/text()").extract_first()
content = div.xpath("./a[1]/div/span//text()").extract()
item = 项目名称Item()
#实例化item对象
item['author'] = author
item['content'] = content
#将解析的数据封装到item对象中
yield item
#将item提交给管道, 提交给的管道是由配置文件中的管道配置的优先级决定的,谁的优先级高就先提交给谁。
```python
items.py 文件
class 项目名称Item(scrapy.Item):
author = scrapy.Field()
content = scrapy.Field()
pipelines.py 文件
class 项目名称PipeLine(object):
fp = None
def open_spider(self,spider):
#重写父类的一个方法,该方法只在开始爬虫的时候被调用一次,因为文件只打开一次就好了
self.fp = open('./123.txt','w',encoding='utf-8')
def process_item(self,item,spider):
#该类用于数据持久化输出,以及相关操作
author = item['author']
content = item['content']
#将爬虫源文件提交过来的数据解析出来
self.fp.write(author+":"+content)
return item
#将item传递给下一个即将执行的管道类,是配置文件中的管道优先级决定的下一个是谁
def close_spider(self,spider):
#重写父类的一个方法,该方法只在结束爬虫的时候被调用一次
self.fp.close()
#关闭文件
#pipelines.py文件中的一个类对应将一组数据存储到某个地方
class mysqlPileLine(object):
#自定义一个类用于爬取的数据存储到数据库
def process_item(self,item,spider):
基于spider的全站数据爬取
将网站中某个板块下的全部页码对应的页面数据进行爬取
- 将所有页码的url添加到start_urls里面(不建议)
制作一个所有页码url的模板,用
yield scrapy.Request(url=url,callback=self.parse)
# callback回调函数 scrapy.Request()发送网络请求,得到应用数据传递给parse函数。
核心组件
- 调度器
- 引擎
- spider
- 管道
- 下载器
spider:产生url,并将url封装成请求对象后,交给引擎,并且接收响应对象进行数据解析和从响应中提取自己需要的信息(也就是item也叫实体),也可以从中解析出链接来,继续进行爬取
引擎:引擎把请求对象交给调度器,接收所有的数据流处理,并且在合适时间触发事务
调度器:由过滤器和队列组成,引擎把请求对象给调度器后,调度器会首先使用过滤器将引擎提交过来的请求对象进行去重,经过去重的请求对象放到队列中,然后调度器从队列中取请求对象交给引擎,然后引擎把请求对象提交给下载器。
下载器:负责去互联网中下载数据,互联网会将响应体返回给下载器,然后下载器将响应对象交给引擎,然后引擎将响应对象交给spider中的parse方法然后进行数据解析,解析后将数据封装到item里给引擎,然后引擎再将item封装好的数据提交给管道进行持久化输出
管道:持久化输出,验证实体的有效性,清除不需要的信息
请求传参
就是将局部的item对象传递到另一个局部函数中(持久化存储的时候使用)
使用场景:爬取解析的数据不在同一张页面中
class BossSpider(scrapy.Spider):
name = 'boss'
start_urls = ['https://www.zhipin.com']
def parse_detail(self,response):
item = response.meta['item']
#取meta传递过来的item对象,是通过response来取值的
job_desc = response.pxath('//#[@id='main']')
item['job_desc'] = job_desc
yield item
#提交给管道
def parse(self,response):
li_list = response.xpath('//*[@id='main']')
for li in li_list:
item= BossproItem()
job_name = li.xpath('.//div[@class='info']')
item['iob_name']=job_name
detail_url = 'https://www.zhipin.com'+li.xpath('.//*[@id='href']')
yield scrapy.Request(deatil_url,callback=self.parse_detail,meta={'item':item})
#meta可以将字典传给scrapy.Request的回调函数
#parse方法负责爬取岗位名称 parse_detail负责爬取岗位职责然后统一持久化
#但是item对象的实例化是在parse方法中实现的,岗位名称可以提交给管道,但是岗位职责是在parse_detail方法中,此方法无法获取到item管道对象,因为是局部变量,此时可以使用请求传参,在parse方法中的手动请求中使用meta进行传递。
scrapy爬取image图片
ImagesPipeline类专门爬取图片的
使用流程:
- 数据解析图片的地址
- 将存储图片地址的管道类提交到指定的管道类(就是自定义的管道类)
- 在管道文件中自定义一个基于IamgesPipeline的管道类,并重写父类方法
- 在配置文件中指定图片的存储位置,并开启自定义的管道
基于scrapy爬取字符串类型的数据和爬取图片类型的数据区别?
- 字符串:只需要基于xpath进行解析且提交管道进行持久化存储
- 图片:xpath解析出图片src属性,单独对图片地址发起请求获取图片二进制类型的数据
ImagePipeline爬取图片:
只需要将img的src属性值进行解析,提交到管道,管道就会对图片的src进行请求发送获取图片的二进制类型的数据,并且还会帮助我们持久化存储
src伪属性,图片软加载
img中只有src属性的时候才会显示图片,有的网站使用了软加载技术,使用src伪属性就是将src修改为src2等,其实src2属性是不生效的。但是链接是真实的,那我们解析数据的时候就需要解析src2属性
软加载就是只有将图片滑动到可视化区域的时候,图片才会被加载出来,此时的src2属性也就变成了src属性,此时图片可以加载出来,
爬虫源文件:
import scrapy
from 项目名称.items import 项目名称Item
class 项目名Spider(scrapy.Spider):
name ='项目名'
start_urls = ['http://sc.chinaz.com/tupian']
def parse(self,response):
div_list = response.xpath("//div[@id="container"]/div")
for div in div_list:
src = div.xpath('./div/a/img/@src2').extract_first()#解析出图片地址
item = 项目名称Item()
item['src'] = src
yield item
items文件
import scrapy
class 项目名称Item(scrapy.Item):
src = scrapy.Field()
管道文件:
#自定义一个类并重写父类的3个方法
from scrapy.pipelines.images import IamgesPipeline
import scrapy
class imagesPipeline(ImagesPipeline):
#自定义的类但是要继承指定的父类
def get_media_requests(self,item,info):
#根据图片地址进行图片数据的请求
yield scrapy.request(item['src'])
def file_path(self,request,response=None,info=None)
#指定图片存储的路径,是根据配置文件中的路径决定的
#request是在get_media_requests中发起请求后得到的请求对象
imageName = request.url.split('/')[-1]
#根据请求的url得到图片名称
return imageName
def item_completed(self,results,item,info):
return item
# 将item管道,返回给下一个即将执行的管道类
settings文件
IMAGES_STORE='路径'
#指定图片的存储路径
ITEM_PIPElINES={
'项目名称.pipelines.imagesPipeline':300,
#将自定义的管道类添加进去,并开启管道
}
中间件
中间件在引擎与下载器之间的叫下载中间件
中间件在引擎与spider之间的叫爬虫中间件
下载中间件可以批量拦截工程中,引擎提交给下载器的请求对象,也可以批量拦截下载器返回给引擎的响应对象
为什么拦截请求?
- 进行ua伪装
在配置文件中设置的ua伪装是针对全局有效的,此时设置的可以单独对某个请求设置 - 代理IP
爬虫文件:
import scrapy
class MiddleSpider(scrapy.Spider):
name = 'middle'
start_urls = ['http://www.baidu.com/s?wd=ip']
def parse(self,response):
page_text = response.text()
print(page_text)
middlewares.py 文件
class MiddleprDownloaderMiddleware(object):
@classmethod
def from_crawler(cls,crawler):
s = cls()
crawler.signals.connect(s.spider_opend,signal=signals.spider_opened)
return s
def process_request(self,request,spider):
#拦截正常的请求
#ua伪装可以写在方法里
request.headers['User-Agent']="UA伪装的内容"
#request.headers获取请求头信息
return None
def process_response(self,request,response,spider):
#拦截所有的响应
#response是拦截到的响应对象
#request是response对象对应的请求对象
#spider 是当前爬虫对象,就是爬虫类实例化出来的
return response
def process_exception(self,request,exception,spider):
#拦截发生异常的请求
#代理ip通常写在此方法里,当请求异常的时候,会进行修订,设置上代理ip进行请求的重新发送
#判断请求是http还是https
if request.url.split(':')[0] == 'http':
request.meta['proxy']='http://ip:port'
else;
request.meta['proxy']='https://ip:port'
return request
#将修订后的请求对象进行重新的发送
def spider_opened(self,spider):
spider.logger.info('Spider opened:%s' % spider.name)
settings文件:
DOWNLOADER_MIDDLEWARES = {
'项目名称.middlewares.MiddleproDownloaderMiddleware':543,
}
#开启下载中间件
为什么拦截响应?
- 篡改响应数据,响应对象
拦截响应案例:
- 爬取网易新闻中的新闻数据 标题和内容
- 通过网易新闻的首页解析出来5个板块对应的详情页url,不是动态加载的
- 每一个板块对应的新闻标题都是动态加载出来的
- 通过解析出每一条新闻详情页的url获取详情页的页面源码,解析新闻内容
爬虫文件:
import scrapy
from selenium import webdriver
from wanyi.items import WangyiproItem
class WangyiSpider(scrapy.Spider):
name="wangyi"
start_urls = ['https://news.163.com']
models_urls = [] #存储详情页的url
def parse(self,response):
#解析五个板块对应的详情页的url
li_list = response.xpath("//*[@id='index2016_wrap']/div[1]/div[2]/div[2]/div[2]")
alist = [3,4,6,7,8]
for index in alist:
model_url = li_list[index].xpath('./a/@href').extract_first()
self.models_urls.append(model_url)
for url in self.models_urls:
yield scrapy.Request(url,callback=self.parse_model)
def parse_model(self,response):
#解析每个板块页面中对应新闻标题和新闻详情页的url
#每个板块对应的新闻标题相关的内容都是动态加载的
div_list = response.xpath('/html/body/div')
for div in div_list:
title = div.xpath('./div/div[1]/h3/a/text()').extract_first()
new_deatil_url = div.xpath('./div/div[1]/h3/a/#href').extract_first()
item = wangyiproItem()
item['title'] = title
yield scrapy.Request(url=mew_detail_url,callback=self.parse_detail,meta={'item':item})
def parse_detail(self,response):
#该方法是解析新闻内容的
content = response.xpath("//*[@id='endText']//text()").extract()
content="".join(content)
item = response.meta['item']
item['content']=content
yield item
def __init__(self):
#实例化一个游览器对象
self.bro = webdirver.Chrome(executable_path='驱动路径')
def closed(self,spider):
self.bro.quit()
middlewares.py文件
from scrapy.http import HtmlResponse
class MiddleprDownloaderMiddleware(object):
def process_response(self,request,response,spider):
#该方法拦截5个板块对应的响应对象,进行篡改
#通过url指定request
#通过request指定response
bro = spider.bro
if request.url in spider.models_urls:
#定位五个板块对应的响应对象,对其进行篡改,实例化一个新的响应对象(包含动态加载的数据)替换就的response(因为板块中的内容是动态加载的,我们直接发起请求得到的响应是不包含动态加载的内容的,此时可以使用selenium发起请求,此时得到的响应对象是包含动态加载的数据的,然后将包含动态加载的响应对象替换为之前没有动态数据的响应对象返回给spider也就是爬虫文件进行下一步的数据处理)
bro.get(request.url)
#发起请求
page_text = bro.page_source
#包含了动态加载的新闻数据
new_response = HtmlResponse(url=request.url,body=page_text,encoding='utf-8',request=request)
#HtmlResponse是构造一个新的响应对象
#body参数是新的响应数据
#url参数是响应对象对应的请求对象的请求url
#request参数响应对象对应的请求对象
return new_response
#返回新的response
else:
#其他请求对象的响应对象
return response
#返回旧的未修改的response
items文件
import scrapy
class WangyiproItem(scrapy.Item):
title = scrapy.Field()
content = scrapy.Field()
CrawlSpider全站数据爬取
- 创建工程
- cd 工程文件
- 创建爬虫文件 ```python scrapy genspider -t crawl xxx www.xxx.com
1.链接提取器:LinkExtractor(allow=r’Items/‘) 去起始url的网页中根据指定规则进行指定链接的提取,allow就是规则,跟的参数是正则表达式,如果提取的链接有重复会自动去重,将链接提取器作用到规则解析器的时候就会自动向链接发起请求并得到响应对象
2.规则解析器:Rule(链接提取器,callback=’回调函数名’,follow=True) 将链接提取器中提取到的链接进行指定规则(就是回调函数)的数据解析操作,链接提取器中有多少个url,回调函数就会被调用多少次,并且将响应对象自动传参给回调函数的response, follow=Ture可以将链接提取器 继续作用到 链接提取器所提取到的链接所对应的页面中,也就是 可以在链接提取器所对应的页面中继续使用该链接提取器 提取链接
```python
爬虫文件:
import scrapy
from scrapy.linkextractors import LinkExtractor #链接提取器
from scrapy.spiders import CrawlSpider,Rule
class SunSpider(CrawlSpider):
name ='sun'
allowed_domains = ['www.baidu.com']
start_urls =['http://www.baidu.com']
rules =(
Rule(LinkExtractor(allow=r'Items/'),callback='parse_item',follow=True)
)#规则解析器
def parse_item(slef,response):
#此方法用于数据解析
i = {}
return i
分布式爬虫
搭建一个分布式的集群,让其对一组资源进行分布联合爬取,可以提升爬取数据的效率
实现分布式爬虫
- 安装scrapy-redis
可以给原生的scrapy框架提供可以被共享的管道和调度器 - 原生的scrapy是无法实现分布式的,必须要scrapy和scrapy-redis组件一起实现分布式。
因为原生的scrapy,调度器和管道不可以被分布式集群共享
实现流程
- 创建工程
- 创建基于CrawlSpider的爬虫文件
- 修改当前的爬虫文件,
导入 scrapy-redis包
将爬虫类的父类修改为RedisCrawlSpider
将 start_urls 和 allowed_domains注释掉
在类中添加 redis_key = “xxx”,可以被共享的调度器队列的名称
编写数据解析相关的操作 ```python 爬虫文件
from scrapy_redis.spiders import RedisCrawlSpider redis_key=””
- 修改配置文件 ,开启可以被共享的管道,指定调度器
```python
配置文件
#开启可以被共享的管道
ITEM_PIPELINES={
'scrapy_redis.piplines.RedisPipeline':400
}
#指定调度器
#增加一个去重容器类的配置,作用是使用redis的set集合来存储请求的指纹数据,从而实现请求去重的持久化
DUPEFILTER_CLASS="scrapy_redis.dupefilter.RFPDupeFilter"
#使用scrapy-redis 组建自己的调度器
SCHEDULER ="scrapy_redis.scheduler.Scheduler"
#配置调度器是否要持久化,也就是当爬虫结束了,要不要清空redis中请求队列和去重指纹的set
SCHEDULER_PERSIST = True
#指定redis服务器 就是数据存储到那台redis服务器
REDIS_HOST ="IP"
REDIS_PORT =6379 #端口
- redis相关操作
配置redis的配置文件:redis.windows.conf
开启redis服务 ```python redis.windows.conf文件
bind 127.0.0.1 注释掉
protected-mode yes 修改为no #关闭保护模式,如果开启的话其他redis客户端只能读数据不能写数据
redis-server 配置文件路径 #启动服务
redis-cli #启动客户端
- 执行工程<br />scrapy runspider 爬虫文件名称
- 向调度器的队列中放入起始的url
```python
调度器的队列在redis客户端中
lpush 调度器队列名称 起始url #就是向调度器的队列中放入起始的url,调度器队列名称就是redis_key
- 爬取的数据存储到了 redis的 项目名称:items这个数据结构中
增量式爬虫
检测网站数据更新情况,只会爬取网站最新更新的数据。
分析
- 指定一个起始url
- 基于CrawlSpider获取其他页码链接
- 基于Rule将其他页码链接进行请求
- 从每一个页码对应的页面源码中解析出来每一个电影详情页的url
- 检测电影详情页的url有没有请求过
将爬取过的电影详情页的url存储到redis的set数据结构中 ```python from redis import redis
conn = Redis(host=”IP”,port=6379)
ex = self.conn.sadd(‘urls’,detail_url)
向redis中添加详情页的url,如果redis中已经有detail_url返回0,没有返回1
```
- 对详情页的url发起请求,然后解析出来电影的名称和简介
- 进行持久化存储