爬虫总结

1. 爬虫流程

  1. 准备 url 列表
  2. 发起请求、获得响应
  3. 提取数据,提取 url 放入 url 列表
  4. 保存数据

聚焦爬虫的流程

注意:提取的数据以 url 对应的响应为准,浏览器 element 只能作为参考



2. requests 的使用

pip install requests

2.1 基本使用
resp = request.get(url,headers,params)
resp = request.post(url,data,headers)

# 原始数据,bytes类型
resp.content
resp.content.decode()

# 根据响应信息进行有规律的推测网页的编码
resp.text
resp.encoding="utf-8"

2.2 保持会话

session类

session = requests.sesssion() # Session()
session.get()
session.post()
# 每次请求后,会读取响应头的set——cookies,并在下次请求时自动携带

Cookie字符串
从浏览器的请求头中复制一份Cookie

headers = {
    Cookie:"xxxx"
}
requests.get(url,headers=headers)

cookies参数
参数类型:dict

cookies_dict = {}
requests.get(url, headers=headers, cookies=cookies_dict)

2.3 设置UA,设置代理
headers = {
    "User-Agent":"xxx"
}
requests.get(url, headers=headers)

proxies = {
    "http":"http://192.168.1.1:80",
    "http":"http://192.168.1.1:80"
}
requests.get(url, headers=headers, proxies=proxies)



3. xpath提取数据

pip install lxml

3.1 xpath语法
//a[@class='next'] 通过属性值定位标签
//a[text()='下一页'] 通过文本定位标签
//a[contains(@class,'next')] 定位 class 属性包含 next 的所有 a 标签
//a[not(@class or @name)] 定位所有不包含 class 属性和 name 属性的 a 标签

/div//text() 提取 div 下面的所有文本
/a/@href  提取属性值
/a/text() 提取文本值

/div/a div下面的 a 标签,a 是 div 的子结点
/div//a  div 下面所有的 a 标签,a 是 div 的后代结点

/a/follow-sibling::*[2]    获取 a 标签下面的所有兄弟结点的第二个
/a/follow-sibling::ul[1]   获取 a 标签下面所有ul的兄弟结点的第一个

3.2 lxml模块的使用
from lxml import etree
el = etree.HTML(str or bytes) # 参数可以是 str 或者 bytes 类型网页源代码
el.xpath("//a[@class='next']") # 返回是元素类型为 element 对象的列表
# element 具有xpath方法
el.xpath("//a/@href") # 返回元素类型为 str 的列表



4. scray框架

4.1 scrapy框架流程

  1. 调用 start_requests() 方法,将 start_urls 中所有的 url 构造成 request 对象,并放入调度器
  2. 引擎从调度器的请求队列中取出一个 request,通过下载器中间件 process_request() 方法,交给下载器
  3. 下载器发起请求,获得响应,通过下载器中间件 process_response() 方法,到达引擎,再通过爬虫中间件的process_response() 交给爬虫
  4. 爬虫提取数据
    3.1 提取出来的是数据,通过引擎交给管道
    3.2 提取出来的是url,构造请求,通过爬虫中间件的process_request()方法,交给调度器
  5. 管道进行数据清洗、数据保存

4.2 scrapy的基本使用
  • scrapy startproject myspider # 创建 scrapy 项目
  • cd myspider
  • scrapy genspider (-t crawl) itcast itcast.cn # 创建项目中的爬虫部分, 指定 -t crawl 即创建 crawlspider
    • 数据提取时需要没层都要提取就使用普通的 spider,进行逻辑实现,手动搓翻页的代码;
    • 数据提取只需要提取详情页的数据,使用 crawlspider 可以实现自己去循环匹配相对应的 url,只有在匹配到满足需求的 url 时,自动的构造自己requests请求,发送给引擎,指定响应处理的方法,对数据进行提取
yield scrapy.Request(url,callback,meta,dont_filter) # url不会补全
# callback 将来url响应的处理函数
# dont_filter 默认false,过滤请求,重复的请求会被过滤

# url 补全
# 有些时候取到的 url 可能会不全,需要对 url 地址去进行拼接 
# 可以手动通过 urllib.parse.urljoin() 进行 url 地址的拼接
# next_url = urllib.parse.urljoin(response.url, next_url)

# 在 follow 方法中封装了 url 地址的拼接,直接使用即可
yield reponse.follow(url,....) # url会自动补全

def parse(self,repsonse):
    item = response.meta['item']
    response.xpath("").extract()  # 匹配到的数据放在一个列表中
    response.xpath("").extract_first()  # 取匹配到的数据列表中的第一个数据,如果没有数据返回 None

4.3 管道
  1. 开启管道 管道用来对数据进行清洗与数据的保存,可以自己手动搓对应的爬虫的管道
    在settting中,添加管道的路径
ITEM_PIPELINES = {
   'suning.pipelines.SuningPipeline': 300, # 设置管道获取数据的优先级,数字越低,优先级越高
}
  1. 方法
process_item(item,spdier)
    if spider.name = "jandan" # spider当前传递item的爬虫对象
        item...
    return item # 如果下一个管道需要数据,必须返回item

# 可以使用如下两个方法来进行数据库的连接与断开、文件的打开与关闭
open_spdier(spider) # 每个爬虫开启的时候会执行一次
    # 数据库的连接初始化

close_spdier(spider) # 每个爬虫关闭的时候执行一次
    # 数据库的关闭

4.4 中间件
1.开启中间件
# SPIDER_MIDDLEWARES = {
#    'suning.middlewares.SuningSpiderMiddleware': 543,
# }

DOWNLOADER_MIDDLEWARES = {
    'suning.middlewares.SeleniumMiddleware': 544,
}
2.中间件的两个方法
process_request(request,spider):
    return None # 1. 继续请求,
    return Request # 2. 请求不再继续,而是放入调度器
    return Response # 3. 请求不再下载,交给爬虫提取数据

process_response(request,response,spdier):
    return Request # 1. 请求放入调度器
    return Response # 2. 继续经过其他中间件的process_response,或者到达引擎,后续交给爬虫处理
3.中间件的功能

设置 UA

process_request(request,spider):
    request.headers['User-Agent'] = random('UA')

设置代理user agent

process_request(request,spider):
    request.meta['proxy'] = 'http://192.168.1.1:80'

设置cookies(主要是为了反反爬)

process_request(request,spider):
    request.cookies = cookies# 可以从cookies池随机取出一个

scrpay集成selenium

open_spdier(spider):
    if spider.name = "jandan":
        spider.driver = webdirver.Chrome()

close_spider(spider)if spider.name = "jandan":
        spider.driver.quit()

process_request(request,spdier)if spider.name = "jandan":
        spdier.dirver.get(request.url)
        return TextResponse(body=spdier.dirver.page_source,request=request,encoding="utf-8",url=spider.dirver.current_url)

4.5 post请求
scrapy.FormRequest(url,callback,formdata)
scrapy.FormRequest.from_response(response,fromdata,callback)

scrapy中默认开启的cookies传递,即本地请求获得的cookies,会在下次请求自动携带

# Disable cookies (enabled by default)
# COOKIES_ENABLED = False



5. 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"

5.1 如何去重

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进制的字符串,称为指纹

  1. 进队
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
  1. 如果请求需要过滤,并且当前请求的指纹已经在指纹集合中存在了,就不能进入队列了
  2. 如果不需要过滤,直接进入队列
  3. 如果请求需要过滤,并且请求的指纹是一个新的指纹,进入队列

5.2 实现分布式爬虫
类需要继承自 RedisSpider、RedisCrawlSpider
redis_key:表示,在redis数据库中存储start_urls的键的名称



6. 数据去重

  1. 中间件去重
process_response(request,response,spider):
    #set可以是内存set集合,也可以是redis的set
    ret = set.add(md5(response.body))
    if ret == 0:
        return request
    else
        return response
  1. 建立复合索引
# 复合索引,加速和去重
stu.ensure_index([("hometown", 1), ("age", 1)], unique=True)
# 根据数据的特征,在mongodb中 对指定字段建立复合索引,所有字段值相同时就无法二次插入了
  1. 布隆过滤器

详情请查看



7.验证码如何搞定?

1.常见验证码,可以利用云打码平台去处理
2.利用selemiun破解极验滑动验证码
  1. 加载页面,获取滑块对象
  2. 获取两个图片(1张是带缺口的图片,1张隐藏的完整的图片)
    • 由于原始图片进行了处理,每一张原图都被拆成52张无序的图片,我们需要找个所有的图片,以及在原图的对应位置,然后将52张图片进行合并
    • 如何合并?创建一个空格图片,将52张图片下载下来,按照指定原图位置填充到空白图片中就可以得到完成的原图
  3. 对比两张图片的像素,找到缺口的左上角位置,这里设定,像素差异值(RGB)大于50,认为满足要求,缺口左上角位置的x 即鼠标需要移动的距离
  4. 这里不能直接控制鼠标匀速移动,因为这样会被极验平台识别是机器操作,所以模拟人的行为先加速后减速,最后到达位置后,随机算出一个偏移量,再挪动一下,因为拖动的距离不能十分精准,否则也容易被识别是机器操作
    • 如何实现先加速后减速?加速度先正后负

详情请参考


3.微博宫格验证码
  1. 这种验证码一般是频繁登陆后或者账号存在异常时才会显示
  2. 首先通过分析,这个宫格只有4个,最多的连接方式位24种,可以先把这24中图片全部下载到本地,根据灰色箭头的指向顺序分别命名这24张图片
  3. 利用selenium进行登陆时,如果弹出验证,可以把本次验证的图片下载到本地,和本地的24张图片进行匹配,设定只要99%的像素差值小于20的,认为两张图片匹配上了
  4. 获取匹配到的图片的文件名,即滑动的顺序确定
  5. 通过xpath找到4个按钮的element,根据滑动顺序,依次完成4个按钮的滑动即可

详情请参考



8. cookie池的维护

8.1 为啥使用cookies?

爬虫中为了获取登陆后页面的数据,必须携带对应的cookies,网站有时候也会根据账号的频繁请求断定当前是一个爬虫程序在请求,可能会进行限制,为了实现反反爬,需要构建cookies池,每次请求都携带不同cookies

8.2 如何实现?
  1. 存储形式:存储在redis中,“spider_name:username–password":cookie

  2. 需要专门创建一个py文件,包含四个方法:

    • initcookies() 初始化所有账号的cookies,将所有账号对用进行登陆获取cookies并保存在redis中
    • update_cookie(spider_name,username,password) # 重新获取账号对应的cookies,并存入redis中
    • remove_cookie(spider_name,usrname,password) # 从redis中删除改账号对应的cookie
    • get_cookie(username,password) # 尝试登陆该账号获取cookies
  3. 在scrapy的下载器中间件(RetryMiddleware)继承自的process_request()随机选择一个cookie,进行设置,并在request的meta中保存该cookies对应的账号

    def process_request(self,request,spider):
        # 获取redis中所有的键(假设redis中只保存了cookies)
        redisKeys = self.rconn.keys()
        elem = random.choice(redisKeys)
        request.cookies = cookie
        # 在请求中记录当前cookies对应的账号和密码
        request.meta["accountText"] = elem.split(":")[-1]
    
  4. 在中间件的process_response()中获取响应,如果响应状态码是301、302等,说明发生页面重定向,那么当前的这个cookies肯定失效了,需要更新或者删除cookies

    def process_response(self,request,response,spider):
        if response.status in [300, 301, 302, 303]:
            # 获取重定向的url
            redirect_url = response.headers["location"]
            if url == "login_url":# 如果是登陆页面,说明当前cookies失效了,需要更新
                username,passworod = request.meta['accountText'].split("--")
                update_cookie(spider_name,username,password)
            elif url=="验证页面":# 说明账号被封了
                username,passworod = request.meta['accountText'].split("--")
                remove_cookie(spider_name,username,password)
            
            # RetryMiddleware中的尝试重新发起请求
            reason = response_status_message(response.status)
            return self._retry(request, reason, spider) or response  # 重试
                
    



9. 爬虫数据的存储

一般是mysql和mongodb

  • 爬取的数据通常都是非结构化数据,这在关系模型的存储和查询上面都有很大的局限性。但爬回来的数据汇总处理后需要在网页上展示,此时可能需要和django项目对接,可以把爬虫数据组织成结构化数据,此时存储在mysql更好一些
  • 根据数据量,200w到2000w的数据量相对来说不是很大,二者都可以。但是基本上数据库达到千万级别都会有查询性能的问 题,MYSQL单表在超过千万级以上性能表现不佳,所以如果数据持续增长的话,可以考虑用mongodb。毕竟mongodb分片集群搭建起来比mysql集群简单多 了,而且处理起来更灵活



10. 常见的反爬虫和应对方法?

  1. 通过Headers反爬虫:
    • 很多网站都会对请求Headers的User-Agent进行检测,还有一部分网站会对Referer进行检测(一些资源网站 的防盗链就是检测Referer)。这个容易解决,之前收集了一大堆的User-Agent,如果用的scrapy框架,就在 爬虫中间件中对reqeust请求添加一个随机的User-Agent,如果是requests库,在请求方法中传入一个包含 User-Agent的headers字典即可
  2. 验证码, 爬虫爬久了通常网站的处理策略就是让你输入验证码验证是否机器人,此时有三种解决方法
    详情请查看
  3. 用户行为检测 有很多的网站会通过同一个用户单位时间内操作频次来判断是否机器人,比如像新浪微博等网站。这种情况 下我们就需要先测试单用户抓取阈值,然后在阈值前切换账号其他用户,如此循环即可。 当然,新浪微博反爬手段不止是账号,还包括单ip操作频次等。所以可以使用代理池每次请求更换不同的代理 ip,也可以考虑维护了一个cooies池,在每次请求时随机选择一个cookies。
  4. ajax请求参数被js加密
    使用selenium + phantomJS,调用浏览器内核,并利用phantomJS执行js来模拟人为操作以及触发页面中的 js脚本。从填写表单到点击按钮再到滚动页面,全部都可以模拟,不考虑具体的请求和响应过程,只是完完整 整的把人浏览页面获取数据的过程模拟一遍。
  5. 分布式爬虫, 分布式能在一定程度上起到反爬虫的作用,当然相对于反爬虫分布式大的作用还是能做到高效大量的抓取
  6. 注意配合移动端、web端以及桌面版 其中web端包括m站即手机站和pc站,往往是pc站的模拟抓取难度大于手机站,所以在m站和pc站的资源相 同的情况下优先考虑抓取m站。同时如果无法在web端抓取,不可忽略在app以及桌面版的也可以抓取到目标 数据资源。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值