mirror of
https://github.com/xishandong/crawlProject.git
synced 2024-11-25 16:34:42 +08:00
451 lines
21 KiB
Plaintext
451 lines
21 KiB
Plaintext
爬虫在使用场景中的分类:
|
||
- 通用爬虫
|
||
抓取系统重要组成部分。抓取的是一整张页面的数据。
|
||
- 聚焦爬虫
|
||
是建立在通用爬虫的基础之上。抓取的是页面中特定的局部内容。
|
||
- 增量式爬虫
|
||
检测网站中数据更新的情况。只会抓取网站中最新更新出来的数据。
|
||
|
||
爬虫的矛与盾:
|
||
- 反爬机制
|
||
门户网站,可以通过制定相应的策略或者技术手段,防止爬虫程序进行网站数据的爬取。
|
||
- 反反爬策略
|
||
爬虫程序可以通过制定相关的策略或者技术手段,破解门户网站中具备的反爬机制,从而获取门户网站中的相关数据。
|
||
|
||
http协议:
|
||
- 概念:服务器与客户端进行数据交互的一种形式。
|
||
- 常用请求头信息:
|
||
(1)User-Agent:请求载体的身份标识。
|
||
(2)Connection:请求完毕后,是断开连接还是保持连接。
|
||
- 常见响应头信息:
|
||
(1)Content-Type:服务器响应回客户端的数据类型
|
||
|
||
https协议:
|
||
- 概念:安全的超文本传输协议
|
||
- 加密方式
|
||
(1)对称密钥加密
|
||
(2)非对称密钥加密
|
||
(3)证书密钥加密
|
||
|
||
requests模块:python中原生的一款基于网络请求的模块,功能强大,简单便捷,效率极高。
|
||
- 作用:模拟浏览器发请求
|
||
- 如何使用
|
||
(1)指定url
|
||
(2)发起请求(Get or Post)
|
||
(3)获取响应数据
|
||
(4)持久化存储
|
||
|
||
动态加载数据:
|
||
网页信息可能是动态加载的,ajax动态请求,可以在XHR中查看真正url
|
||
|
||
数据解析:
|
||
- 正则
|
||
- bs4
|
||
- xpath(***)
|
||
|
||
数据解析原理:
|
||
大部分文本内容都存在标签中或者标签对应的属性中
|
||
进行指定标签定位
|
||
标签或标签对应的属性中存储的数据值进行提取(解析)
|
||
|
||
聚焦爬虫编码流程:
|
||
- 指定url
|
||
- 发起请求
|
||
- 获取响应数据
|
||
- 数据解析
|
||
- 持久化存储
|
||
|
||
正则表达式:(import re)
|
||
- 单字符:
|
||
. : 除换行以外的所有字符
|
||
[] : [aoe] [a-w] 匹配集合任意一个字符
|
||
\d : 数字 [0-9]
|
||
\D : 非数字
|
||
\w : 数字,字谜,下划线,中文
|
||
\W : 非\w
|
||
\s : 所有的空白字符,包括空格,制表符,换页符等等。等价于 [\f\n\r\t\v]
|
||
\S : 非空白
|
||
- 数量修饰:
|
||
* : 任意多次 >=0
|
||
+ : 至少依次 >=1
|
||
? : 可有可无,0次或1次
|
||
{m} : 固定m次 hello{3, }
|
||
{m,} : 至少m次
|
||
{m,n} : m-n次
|
||
- 边界:
|
||
$ : 以某某结尾
|
||
^ : 以某某开头
|
||
- 分组:
|
||
(ab)
|
||
- 贪婪模式: .*
|
||
- 非贪婪模式: .*?
|
||
- re.I: 忽略大小写
|
||
- re.M: 多行匹配
|
||
- re.S: 单行匹配
|
||
- re.sub(正则表达式,替换内容, 字符串)
|
||
|
||
<div class="pic">
|
||
<a href="https://www.douban.com/photos/album/1727324287/">
|
||
<img src="https://img1.doubanio.com/view/photo/albumcover/public/p2578730628.webp"
|
||
data-origin="https://img1.doubanio.com/view/photo/albumcover/public/p2578730628.webp" alt="">
|
||
</a>
|
||
</div>
|
||
|
||
ex = '<div class="pic">.*?<img src=.*? data-origin="(.*?)" alt=.*?</div>'
|
||
|
||
bs4进行数据解析:
|
||
- 数据解析原理:
|
||
标签定位
|
||
提取标签、标签属性的数据值
|
||
- bs4数据解析原理:
|
||
1.实例化一个BeautifulSoup对象,并将页面源码数据加载到该对象中
|
||
2.通过调用BeautifulSoup对象中相关属性或者方法进行标签定位和数据提取
|
||
- 进行环境的安装:
|
||
pip install bs4
|
||
pip install lxml
|
||
- 如何实例化BeautifulSoup对象:
|
||
1.from bs4 import BeautifulSoup
|
||
2.对象的实例化
|
||
- 将本地的html文档中的数据加载到该对象中
|
||
fp = open('./test.html', 'r', encoding='utf-8')
|
||
soup = BeautifulSoup(fp, 'lxml')
|
||
- 将互联网上获取的页面源码加载到该对象中
|
||
page_text = response.text
|
||
soup = BeautifulSoup(page_text, 'lxml')
|
||
- 提供的用于数据解析的方法和属性:
|
||
soup.tagName: 返回的是文档中出现的第一个tagName标签
|
||
soup.find()
|
||
- soup.find('tagName'): 相当于soup.tagName
|
||
- soup.find('tagName', class_='song'): 属性定位
|
||
- soup.find_all('tagName'): 返回符合要求的所有标签,是一个列表
|
||
soup.select():
|
||
- select('某种选择器'): 返回的是一个列表
|
||
- 层级选择器:
|
||
soup.select('.tang > ul > li > a') > 表示一个层级
|
||
soup.select('.tang > ul a') ' '空格表示多个层级
|
||
- 获取标签中的文本数据
|
||
soup.a.text/.strong/.get_text():
|
||
text/get_text(): 可以获取某一个标签中的所有文本内容
|
||
string: 只可以获取该标签下面直系的文本内容
|
||
- 获取标签中属性值
|
||
soup.a['属性名']
|
||
|
||
xpath解析: 最常用且最便捷高效的一种解析方式,通用性强。
|
||
- 原理:1.实例化一个etree对象,且需要将被解析的页面源码数据加载到该对象中。
|
||
2.调用etree对象中的xpath方法结合着xpath表达式实现标签的定位和内容的捕获。
|
||
- 环境的安装:
|
||
pip install lxml
|
||
- 如何实例化etree对象: from lxml import etree
|
||
将本地的html文档中的数据加载到该对象中
|
||
etree.parse(filePath)
|
||
将互联网上获取的页面源码加载到该对象中
|
||
etree.HTML(page_text)
|
||
- xpath("xpath表达式")
|
||
/: 表示从根节点开始定位,一个/表示一个层级.
|
||
//: 表示多个层级(可以表示从任意位置定位)
|
||
属性定位: //div[@class="song"] tag[@attrNAme="attrValue"]
|
||
索引定位: tree.xpath('//div[@class="song"]/p[3]') 索引是从1开始的
|
||
取文本:/text() 只能取到标签的直系文本 //text() 可以取到标签中非直系的文本内容(所有文本内容)
|
||
取属性:/@attrName ==>img/@src
|
||
局部解析: title = li.xpath('./a/div[2]//h3/text()')[0] 一定要加"."
|
||
|
||
验证码识别:
|
||
- 识别验证码操作:
|
||
- 人工肉眼识别。(不推荐)
|
||
- 第三方自动识别。(推荐)
|
||
- ddddocr库:
|
||
import ddddocr
|
||
ocr = ddddocr.DdddOcr()
|
||
with open('1.png', 'rb') as f:
|
||
img_bytes = f.read()
|
||
res = ocr.classification(img_bytes)
|
||
print(res)
|
||
|
||
模拟登录:
|
||
- 爬取基于某些用户的用户信息。
|
||
- 点击登录按钮之后可能会发起一个post请求
|
||
- post请求中会携带相关的用户信息(用户名,密码,验证码....)
|
||
- 页面会更新_VIEWSTATE 页面隐藏域和__VIEWSTATEGENERATOR 页面隐藏域时,我们需要对这个数据也进行爬取
|
||
viewstate = tree.xpath("//input[@id='__VIEWSTATE']/@value")[0]
|
||
viewstategenerator = tree.xpath("//input[@id='__VIEWSTATEGENERATOR']/@value")[0]
|
||
EVENTVALIDATION = tree.xpath("//input[@id='__EVENTVALIDATION']/@value")
|
||
- 我们一次只能用requests发一次请求,之后再需要发请求时,用Session(),将请求包装成一个对象,这样就不会导致访问失败
|
||
session = requests.Session()
|
||
code_data = session.get(url=code_img_src, headers=headers).content
|
||
http/https协议特性: 无状态
|
||
- 发起的第二次基于个人页面请求的时候,服务器端并不知道此请求是基于登录状态下的请求
|
||
- cookie: 用来让服务器端记录客户端的相关状态
|
||
- 自动处理: cookie值的来源: 登陆时post请求中携带有cookie值
|
||
session会话对象:
|
||
- 作用:
|
||
- 可以进行请求的发送
|
||
- 如果请求过程中产生了cookie,则该cookie会被自动存储携带在该session对象中
|
||
|
||
代理: 破解封IP这种反爬机制
|
||
什么是代理:
|
||
- 代理服务器
|
||
代理的作用:
|
||
- 突破自身ip访问的限制
|
||
- 可以隐藏自身真实的ip
|
||
代理IP的匿名度:
|
||
- 透明:服务器知道使用了代理,知道真实ip
|
||
- 匿名:服务器知道使用了代理,不知道真实IP
|
||
- 高匿:服务器不知道使用了代理
|
||
|
||
高性能异步爬虫:
|
||
- 异步爬虫的方式:
|
||
- 多线程,多进程(不建议): 无法无限制的开启多线程和多进程。
|
||
- 进程池、线程池:池的容量是有上线的。
|
||
- 单线程 + 异步协程(推荐):
|
||
- event_loop: 事件循环,相当于一个无限循环,我们可以把一些函数注册到这个事件循环上,当满足某些条件时,函数就会被循环执行。
|
||
- coroutine: 协程对象,我们可以将协程对象注册到事件循环中,它会被事件循环调用。我们可以使用async关键字来定义一个方法,这个方法在调用的时候不会立即执行,而是返回一个协程对象。
|
||
- task: 任务,它是对协程对象的进一步封装,包含了任务的各个状态。
|
||
- future: 代表将要执行或还没有执行的任务,实际上和task没有本质区别。
|
||
- async: 定义一个协程。
|
||
- await: 用来挂起阻塞方法的执行。
|
||
|
||
|
||
selenium模块的基本使用:
|
||
- 下载selenium: pip install selenium
|
||
- 下载一个浏览器驱动程序(谷歌)
|
||
- 下载路径:http://chromedriver.storage.googleapis.com/index.html
|
||
- 实例化一个浏览器对象
|
||
- 编写基于浏览器自动化操作代码
|
||
- 发起请求: get(url)
|
||
- 标签定位: find系列方法
|
||
- 标签交互: send_keys('xxx')
|
||
- 执行js程序: execute_script('jsCode')
|
||
- 前进,后退: back(),forward()
|
||
- 关闭浏览器: quit()
|
||
- selenium处理iframe
|
||
- 如果定位的标签存在iframe标签之中,则必须使用switch_to.iframe(id)
|
||
- 动作链: from selenium.webdriver import ActionChains
|
||
- 实例化一个动作链对象: action = ActionChains(bro)
|
||
- 执行操作: action.click_and_hold(div) action.move_by_offset(17, 0).perform()
|
||
- 释放对象: action.release()
|
||
|
||
scrapy框架
|
||
- 什么是框架?
|
||
- 集成了很多功能并且具有很强通用性的一个项目模板。
|
||
- 如何学习框架?
|
||
- 专门学习框架封装的各种功能的详细用法。
|
||
|
||
- 什么是scrapy?
|
||
- 爬虫中封装好的一个明星框架。
|
||
- 功能: 高性能的持久化存储,异步的数据下载,高性能的数据解析操作,分布式
|
||
- 基本使用
|
||
- 环境安装: mac/linux: pip install scrapy
|
||
: windows: pip install wheel
|
||
下载twisted,下载地址为: http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
|
||
下载twisted: pip install Twisted-20.3.0-cp39-cp39-win_amd64.whl
|
||
pip install pywin32
|
||
pip install scrapy
|
||
- 创建一个工程: scrapy startproject xxxPro
|
||
- cd xxxPro
|
||
- 在spiders子目录中创建一个爬虫文件
|
||
- scrapy genspider spiderName www.xxx.com
|
||
- 执行: scrapy crawl spiderName(scrapy crawl test --nolog 采用无日志信息输出,但是这样不好,我们使用接下来的方法)
|
||
- 在配置文件中添加: LOG_LEVEL = 'ERROR' 表示只输出错误信息
|
||
- 修改user-agent: USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36'
|
||
- scrapy数据解析
|
||
- 在parse(self, response)函数中进行编写,详细操作方法可见qiubai案例
|
||
- scrapy持久化存储
|
||
- 基于终端命令的持久化存储:
|
||
- 要求: 只可以将parse方法的返回值存储到本地的文本文件中
|
||
- 注意持久化存储的文件类型只可以为 'json', 'jsonlines', 'jl', 'csv', 'xml', 'marshal', 'pickle'
|
||
- scrapy crawl xxx -o filePath
|
||
- 优点: 简介高效便捷
|
||
- 缺点: 局限性比较强(数据只可以存储到指定文件后缀的文本文件中)
|
||
- 基于管道持久化存储:
|
||
- 数据解析
|
||
- 在item类中定义相关的属性
|
||
- 将解析到的数据封装到item类型的对象
|
||
- 将item类型的对象提交给管道进行持久化存储
|
||
- 在管道类的process_item中要将其接受到的item对象中存储的数据进行持久化存储操作
|
||
- 在配置文件中开启管道
|
||
- 好处:通用性强
|
||
- 爬虫文件提交的item类型对象最终会提交给哪一个管道类?
|
||
- 先执行的管道类
|
||
|
||
面试题: 将爬取到的数据一份存储到本地一份存储到数据库,如何实现?
|
||
- 管道文件中一个管道类对应的是将数据存储到一种平台
|
||
- 爬虫文件提交的item只会给管道文件种第一个被执行的管道类接受
|
||
- process_item中的return item表示item将会被传递给下一个即将被执行的管道类
|
||
|
||
基于spider的全站数据爬取
|
||
- 就是将网站下的某板块下的全部页码对应的页面数据进行爬取
|
||
- 爬取笑话网的段子数据
|
||
- 将所有页面的url添加到start_urls列表(不推荐)
|
||
- 自行手动进行请求发送
|
||
|
||
scrapy五大核心部件
|
||
- 管道
|
||
- 与引擎交互,之后持久化存储
|
||
- spider
|
||
- 爬虫文件中的爬虫类
|
||
- 调度器
|
||
- 过滤器: 过滤重复的请求对象
|
||
- 队列: 存放请求对象
|
||
- 下载器
|
||
- 与互联网通信,数据下载
|
||
- 引擎
|
||
- 接收队列以及发送response
|
||
- 所有类都要经过引擎
|
||
- 核心作用: 用作数据流处理以及触发事件
|
||
|
||
请求传参
|
||
- 使用场景: 爬取解析的数据不在同一张页面中。(深度爬取)
|
||
- 需求: 爬取boss直聘的岗位名称,岗位描述
|
||
|
||
图片数据爬取之ImagePipeline
|
||
- 基于scrapy爬取字符串类型数据和图片类型数据的区域?
|
||
- 字符串: 只需要基于xpath解析且提交管道进行持久化存储
|
||
- 图片: 我们只能解析到图片地址,之后我们要单独对图片地址发起请求获取图片二进制类型数据
|
||
- 基于ImagePipeline:
|
||
- 只需要将img的src属性值提交给管道,管道就会对图片的src进行请求发送获取图片的二进制类型的数据,且还会进行持久化存储
|
||
- 需求: 爬取站长素材中的高清图片
|
||
|
||
中间件
|
||
- 下载中间件
|
||
- 位置: 引擎和下载器之间
|
||
- 作用: 批量拦截到整个工程中所有的请求和响应
|
||
- 拦截请求:
|
||
- 请求头信息(UA伪装, 代理ip)
|
||
- 拦截响应:
|
||
- 篡改响应数据,响应对象
|
||
|
||
CrawlSpider: 类,Spider的一个子类
|
||
- 全站数据爬取的方式
|
||
- 基于spider实现: 手动请求发送
|
||
- 基于CrawlSpider实现
|
||
- CrawlSpider的使用:
|
||
- 创建一个工程
|
||
- 创建爬虫文件: scrapy genspider -t crawl xxx www.xxx.com
|
||
- 链接提取器
|
||
- 作用: 根据指定规则(allow=r'正则表达式')进行指定链接提取
|
||
- 规则解析器
|
||
- 作用: 将链接提取器提取到的链接进行指定规则(callback)的解析操作
|
||
|
||
分布式爬虫
|
||
- 概念: 我们需要搭建一个分布式机群,让其对一组资源进行分布联合爬取
|
||
- 作用: 提示爬取数据的效率
|
||
- 如何实现分布式?
|
||
- 安装一个scrapy-redis的组件
|
||
- 原生的scrapy是不可以实现分布式爬虫,必须要让scrapy结合这scrapy-redis组件一起实现分布式爬虫
|
||
- 为什么原生的scrapy不可以实现分布式爬虫?
|
||
- 不同电脑的scrapy调度器不能共享
|
||
- 管道不可以被分布式机群共享
|
||
- scrapy-redis组件的作用:
|
||
- 可以给原生的scrapy框架提供可以被共享的管道和调度器
|
||
- 实现流程
|
||
- 创建一个工程
|
||
- 创建一个基于crawlspider的爬虫文件
|
||
- 修改当前的爬虫文件:
|
||
- from scrapy_redis.spiders import RedisCrawlSpider
|
||
- 将start_url allowed_domain进行注释
|
||
- redis_key = 'name' 可以被共享的调度器队列的名称
|
||
- 编写相关的数据解析操作
|
||
- 将当前爬虫类的父类修改成RedisCrawlSpider
|
||
- 修改配置文件
|
||
- 指定可以被共享的管道:
|
||
- ITEM_PIPELINES = {
|
||
'scrapy_redis.pipelines.RedisPipeLine': 400,
|
||
}
|
||
- 指定调度器
|
||
- DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDuperFilter'
|
||
- SCHEDULER = 'scrapy_redis.scheduler.Scheduler'
|
||
- SCHEDULER_PERSIST = True
|
||
- 指定redis服务器
|
||
- REDIS_HOST = '127.0.0.1' # 服务器ip
|
||
- REDIS_PORT = 6379
|
||
- redis相关操作配置:
|
||
- 配置redis的配置文件:
|
||
- linux或者mac: redis.conf
|
||
- windows: redis.windows.conf
|
||
- 打开配置文件修改:
|
||
- 注释绑定: # bind 127.0.0.1
|
||
- 关闭保护模式: protected-mode no
|
||
- 结合配置文件启动redis数据库
|
||
- redis-server 配置文件
|
||
- ./redis-cli
|
||
- 执行工程
|
||
- scrapy runspider xxx.py
|
||
- 向调度器的队列中放入起始url
|
||
- 调度器的队列在redis客户端中
|
||
- lpush xxx www.xxx.com
|
||
|
||
增量式爬虫
|
||
- 概念: 检测网站数据更新情况,只会爬取网站最新更新出来的数据
|
||
- 分析:
|
||
- 起始url
|
||
- 基于CrawlSpider获取其他页面链接
|
||
- 基于Rule将其他页码链接进行请求
|
||
- 从每一个页面对应的页面源码中解析出每一部电影详情页的url
|
||
|
||
- 核心: 检测详情页的url之前有没有请求过
|
||
- 将爬取过的电影详情页url存储
|
||
- 存储到redis的set数据库
|
||
conn = Redis(host='127.0.0.1', port=6379)
|
||
|
||
ex = self.conn.sadd('urls', detail_url)
|
||
if ex == 1:
|
||
print('该url没有被爬取过,可以进行数据爬取!'
|
||
yield scrapy.Request(detail_url, callback=self.parse_detail)
|
||
else:
|
||
print('数据还没有更新,暂无新数据!')
|
||
|
||
class PipeLine(object):
|
||
conn = None
|
||
def open_spider(self, spider):
|
||
self.conn = spider.conn
|
||
def process_item(self, item, spider):
|
||
dic = {
|
||
'name': item['name'],
|
||
'desc': item['desc'],
|
||
}
|
||
print(dic)
|
||
self.conn.lpush('movieData', dic)
|
||
return item
|
||
|
||
- 对详情页的url发起请求,然后解析出电影的名称和时间
|
||
- 进行持久化存储
|
||
|
||
scrapy_splash
|
||
- 使用scrapy_splash最终拿到的response相当于在浏览器全部渲染完成之后的网页页面
|
||
- 作用: 模拟浏览器加载js,并返回js运行后的数据
|
||
- 安装环境:
|
||
- 安装docker
|
||
- sudo docker pull scrapinghub/splash
|
||
- 尝试运行镜像:
|
||
- 在前台运行: sudo docker run -p 8050:8050 scrapinghub/splash
|
||
- 在后台运行: sudo docker run -d -p 8050:8050 scrapinghub/splash
|
||
|
||
JS逆向
|
||
- 数据加密
|
||
- 看到的是一堆密文
|
||
- 请求头加密
|
||
- 表单加密
|
||
- 模拟生成规则,在被加密前是什么内容
|
||
- 参数加密
|
||
- cookie加密
|
||
- 通常是在浏览器有正确地响应,但是爬虫返回的是一堆js代码或者非正常的响应
|
||
|
||
Web逆向技巧
|
||
- 爬虫的接口定位
|
||
- 字体加密;Unicode编码;数据加密
|
||
- 无混淆的js
|
||
- 关键字搜索
|
||
- 解密搜decrypt
|
||
- 加密搜encrypt
|
||
- ajax渲染搜JSON.parse (JSON.parse(函数或者方法(密文),a = 函数或者方法(密文)|JSON.parse(a)
|
||
- 搜接口自带的关键字(特点:方法或者函数包裹密文数据)
|
||
- xhr断点
|
||
- 路径搜索
|
||
- 跟栈
|
||
- hook
|
||
- 反debug: 内存写入变量
|
||
- 注入: 控制台注入 本地替换
|
||
|
||
|
||
1. html -- lxml -- re
|
||
2. json -- 如何提取键值以及组装成自己想要的样子 |