python爬虫

爬虫入门

curl的使用(linux系统下)

参数说明实例
-A设置user-agentcurl -A “chorme” http://www.baidu.com
-X用指定方法请求curl -X POST http://www.baidu.com
-I只返回请求的头信息curl -I https://www.baidu.com
-d以POST方法请求url,并发送相应参数curl -d a=1 -d b=2 -d c=3
curl -d “a=1&b=2&c=3”curl -d @文件路径 http://httpbin.org/
-O下载文件并以远程的文件名保存curl -O http://httpbin.irg/image.jpeg
-o下载文件并以指定的文件名保存curl -0 fox.jpeg http://httpbin.irg/image.jpeg
-L跟随重定向请求curl -IL https://www.baidu.com
-H设置头信息curl -o image.webp -H “accept:image/webp” http://httpbin.org/image
-K允许发起不安全的SSL请求
-b发起一个带cookie的请求curl -b a=test http://httpbin.org/cookies

-s 不显示详细信息

反爬机制

	- 图片冷加载
		- 只有当图片被显示在浏览器可视化范围内,才会将img的伪属性变成真正的属性。如果是requests发送请求,requests请求是没有可视化范围的,因此我们一定要解析的是img伪属性的属性值(图片地址)
	- Robots
	- User-Agent
	- 动态数据加载
	- cookie
		- 方式一:手动处理
			- 将抓包工具中的cookie粘贴到headers中
			- 弊端:cookie如果过了有效时长则该方式失败.
		- 方式二:自动处理
			- 基于Session对象实现自动处理。
			- 如何获取一个Session对象:
				- requests.Session()返回一个session对象
			- session对象操作
				- 该对象可以像requests一样调用get和post发起指定的请求,只不过如果在使用session发送请求的过程中如果产生了cookie,则cookie会被自动存储到该session对象中,那么就意味着下次再次使用session对象发起请求,则该次请求就是携带了cookie进行的请求发送
			
		- 在爬虫中使用session对象至少2次
			- 第一次使用session对象是为了捕获cookie且存储到session对象中。
			- 第二次的时候就是携带cookie进行的请求

代理操作

- 在爬虫中,所谓的代理就是代理服务器
- 代理服务器的作用:
	- 转发请求和响应
- 在爬虫中为何使用代理服务器
	- 如果我们的爬虫在短时间内对服务器发起了高频的请求,那么服务器会检测到这样一个异常的行为请求,就会将该请求对应的设备的ip禁掉,就以为client设备无法对服务器端再次进行请求发送(ip被禁掉)
	- 如果IP被禁,就可以使用代理服务器进行请求转发,破解掉IP被禁掉的反爬的机制.因为使用代理后,服务器端接受到的请求对应的IP地址就是代理服务器而不是我们真正客户端的.
- 代理服务器商分为不同的匿名度
	- 透明代理:如果使用该形式的代理,那么服务器端知道你使用了代理机制也知道你的真实ip。
	- 匿名代理:如果使用该形式的代理,那么服务器端知道你使用了代理机制不知道你的真实ip。
	- 高匿代理:如果使用该形式的代理,那么服务器端不知道你使用了代理机制也不知道你的真实ip。
- 代理的类型:
	- https:代理只能转发https协议的请求
	- http:只能转发http协议的请求
- 代理服务器:
	- 快代理:https://www.kuaidaili.com
	- goubanjia:http://www.goubanjia.com/index.html
	- 智连代理:https://www.zhiliandaili.cn(推荐)
- xpath里不能出现tbody
- requests里面是proxies={'http': ip:prot}	proxy = {'http': '121.233.206.89:9999'}
- scrapy:
	- 在process_requests方法里写上
		- request.meta['proxy] = 'http://121.233.206.89:9999'

验证码的识别

- 基于线上的打码平台识别验证码
- 打码平台
	- 超级鹰(推荐)
		- 唯一一个可以识别12306的平台
	- 云打码
	- 打码兔

模拟登录

- 流程
	- 对点击登陆按钮对应的请求包进行发送(POST请求)
	- 处理请求参数:
		- 用户名
		- 密码
		- 验证码
		- 其他的防伪参数
	- 在请求参数中看到一组乱序的请求参数,最好去验证这组请求参数是否为动态变化
		- 方法一:常规来说一般动态变化的请求参数会被隐藏在前台界面中,那么我们就要去前台页面源码中找。
		- 方法二:如果前台页面没有的话,我们就可以基于抓包工具进行全局搜索。

基于百度AI实现的爬虫功能

- 图像识别
- 语言识别&合成
- 自然语言处理

selenium

- 概念:基于浏览器自动化的模块
- 自动化:可以通过代码指定一些列的行为动作
- selenium和爬虫之间的关系
	- 1. 便捷的捕获到任意形式动态加载的数据(可见即可得)
	- 2. 实现模拟登录
- 弊端
	- 效率变低
- 动作链:一系列连续的动作
- 如何让selenium规避检测
	- 有的网站会检测请求是否为selenium发起,如果是的话则让该次请求失败
	- 规避检测的方法:
		- 使用浏览器托管

selenium接管chrome浏览器实现规避检测

	- 我们可以使用Chrome DevTools协议。它允许客户检查和调试Chrome浏览器。
		- 1. 打开cmd,命令行中输入:
		- chrome.exe --remote-debugging-port=9222 --user-data-dir="C:\selenum\AutomationProfile"
		- 对于 -remote-debugging-port的值,可以指定任意窗口
		- 对于user-data-dir标记,指定创建新Chrome配置文件的目录,
		- 他是为确保再单独的配置文件中启动Chrome,不会污染你的默认配置文件。
		- 还有,不要忘记在系统变量PATH中将Chrome的路径装进去
		- 此时会打开一个浏览器页面,我们输入一个百度网址,我们把它当作一个已存在的浏览器
		
	- 实现步骤:
		1. 找到谷歌浏览器的驱动找到,并将其添加到系统变量PATH中
		2. 窗口上输入:
		3. chrome.exe --remote-debugging-port=9222 --user-data-dir="一个空文件夹的目录"
			2.1 执行结束后,会打开你本机安装好的谷歌浏览器
		4. 执行如下代码:可以使用下面代码接管步骤2打开的真实浏览器
  • 无头浏览器(无可视化界面的浏览器)
    • 谷歌无头浏览器(推荐)
    • phantomJs

js爬虫操作

js解密+混淆破解

	- 响应数据时加密的密文数据
	- 问题:该数据包请求到的是密文数据,为何在前台页面显示的是原文数据
	- 原因:请求请求到密文数据后,前台接受到密文数据后使用指定操作(js函数)对密文数据进行解密,然后将原文数据显示在前台
	- 解决方法:
		- 首先先处理动态的请求参数,动态获取该参数后,就可以携带该参数进行请求发送,将请求到的密文数据捕获到。
		- 将捕获到的密文数据找到对应的解密函数进行解密即可。	
		-【重点】找到点击查询按钮后对应的ajax请求代码,从这组代码中就可以得到动态变化的请求参数和加密的响应数据对应的相关操作。
		- 找ajax请求对应的代码,分析代码获取参数d的生成,和加密的响应数据的解密操作
			- 基于火狐浏览器定位查询按钮绑定的点击事件(火狐F12下元素绑定的事件(event)可以查到)
			- 从绑定函数获取解密代码!
				- 可能函数会调用其他函数,分析内置代码,直至找到ajax代码
				- 有的代码不在同一个文件中,需要在抓包工具(Network)中进行全局搜索
				- 找到的函数实现被加密了
					- 对加密函数进行解密
					- js混淆:对核心的js代码进行加密
					- js反混淆:对核心的js代码进行解密
						- 暴力破解:https://www.dingk.cn/jsConfusion/
						- 终于看到ajax的代码
						- 分析结论
							- data:返回的加密响应数据
								- decode():有类似的解密函数,用于解密data
							- param:动态变化且加密的请求参数
								- getParam():获取动态加密请求参数

js逆向

- 现在需要调用两个函数(deocde,getParam)返回结果即可。在python程序中如何执行js代码
- js逆向:在python调用js代码
- 使用PyExecJS库来实现模拟JavaScript代码执行获取动态加密的请求参数,
- 然后再将加密的响应数据带入decode进行解密即可
	- pip install PyExecJS
	- 在本机中装好nodejs环境
- 有的函数不能在外面直接调用,需要自己定义一个接口函数,注意数据类型
	import execjs
	# Cnode = execjs.get()
	Params
	a = 1
	b = 2

	# Compile JavaScript
	file = 'jsCode.js'
	ctx = node.compile(open(file, encoding='utf-8').read())

	# CGetResult
	js = "add({}, {})".format(a,b)
	result = ctx.eval(js)
	print('result===={}'.format(result))

异步爬虫

 Flask的基本使用
	- 环境安装:pip install flask
	- 创建一个py源文件

代码:

from flask import Flask, render_template
import time
# 实例化一个app
app = Flask(__name__)

# 创建视图函数和路由地址
@app.route('/bobo')
def index_1():
    return 'Hello world!'

@app.route('/jay')
def index_2():
    time.sleep(2)
    return render_template('test.html')


@app.route('/tom')
def index_3():
    time.sleep(2)
    return render_template('test.html')
@app.route('/jike')
def index_4():
    time.sleep(2)
    return render_template('test.html')


if __name__ == '__main__':
    # debug=True表示开启调试模式:服务器端代码被修改后按下保存键会自动重启服务
    app.run(debug=True)

基于线程池

- 线程池
	- from multiprocessing.dummy import Pool
	- map(callback, alist)
		- 可以使用callback对alist中的每一个元素进行指定形式的异步操作
import requests
import time
from multiprocessing.dummy import Pool
def get_request(url):
    return len(requests.get(url=url).text)

urls = [
        'http://127.0.0.1:5000/jay',
        'http://127.0.0.1:5000/jike',
        'http://127.0.0.1:5000/tom'
]
# 同步代码
# if __name__ == '__main__':
#     start = time.time()

#     for url in urls:
#         res = get_request(url)
#         print(res)
#     print('总耗时:{}'.format(time.time()-start))

if __name__ == '__main__':
    start = time.time()
    pools = Pool(3)# 3表示开启线程的数量
    # pools.map(func, alist)
    # func为回调函数,需要基于异步的形式对alist中的每一个列表元素进行操作
    # 保证回调函数必须有要有一个参数和返回值
    result_list = pools.map(get_request, urls)
    print(result_list)
    print('总耗时:{}'.format(time.time()-start))

基于单线程+多任务的异步爬虫

pip install asyncio

- 特殊的函数
	- 如果一个函数的定义被async修饰后,则该函数变成一个特殊的函数
	- 特殊之处
		- 该特殊函数调用后,函数内部的实现语句不会被立即执行
		- 该特殊函数被调用后会返回一个协程对象
- 协程对象
	- 对象。通过特殊函数的调用返回一个协程对象
	- 协程 == 特殊函数 == 一组指定的操作
	- 协程 == 一组指定的操作
- 任务对象
	- 任务对象就是一个高级的协程对象,就是对协程对象的进一步封装
	- 任务 == 协程 == 特殊函数 == 一组指定的操作
	- 任务 == 一组指定的操作
	- 如何创建一个任务对象:
		- asyncio.ensure_future(协程对象)
	- 任务对象的高级之处:
		- 可以给任务对象绑定回调:
			- task.add_done_callback(task_call_back)
			- 回调函数的调用时机:
				- 任务被执行结束后,才可以调用回调函数
			- 回调函数的参数只能有1个:表示的就是该回调函数的调用者(任务对象)
			- 使用回调函数的参数调用result()返回就是任务对象表示的特殊函数return的结果
- 事件循环对象
	- 对象。
	- 作用:
		- 可以将多个任务对象注册/装载到事件循环对象中
		- 如果开启了事件循环后,则其内部注册/装载的任务对象表示的指定操作就会被基于异步的被执行
	- 创建方式:
		- loop = asyncio.get_event_loop()
	- 注册且启动方式
		- loop.run_until_complete(task)
- wait方法是干什么的?
	- 可以将任务列表中的任务对象进行可挂起操作,只有任务对象被赋予了可被挂起的权限后,该任务对象才可以被挂起
	- 任务对象挂起:将当前挂起的任务对象交出cpu的使用权。
	- 只有当任务对象的cpu的使用权交出后,loop才可以使用cpu去执行下一个任务对象。
- 注意事项【重要】:
	- 在特殊函数内部不可以出现不支持异步模块对应的代码,否则会中断整个异步效果,如time.sleep,requests
	
- await关键字
	- 在特殊函数内部,凡是阻塞操作前都必须使用await进行修饰。await就可以保证阻塞操作在异步执行过程中不会被跳过!
	- requests不支持异步效果
- aiohttp:pip install aiohttp
	- 是一个支持异步的网络请求模块
	- 使用代码:
		- 先写出一个大致的架构
async def get_request(url):
	# 实例化好了一个请求对象
	with aiohttp.ClientSession() as sess:
	# 调用get发送请求,返回一个响应对象
	# get/post(url, headers,params/data, proxy='http://ip:port')
	with sess.get(url=url) as response:
	# 获取字符串形式的响应数据
	page_text = response.text()
		- 补充细节:在堵塞处加上await关键字
		- 完整代码:
async def get_request(url):
# 实例化好了一个请求对象
	async with aiohttp.ClientSession() as sess:
	# 调用get发送请求,返回一个响应对象
	# get/post(url, headers,params/data, proxy='http://ip:port')
	async with await sess.get(url=url) as response:
	# text()获取字符串形式的响应数据
	# read()获取byte类型的响应数据
	page_text = await response.text()
	return page_text
- 多任务爬虫的数据解析
	- 一定要使用任务对象的回调函数实现数据解析
	- why:
		- 多任务的架构中数据的爬取是封装在特殊函数中的,
		- 我们一定要保证数据请求结束后,再实现数据解析

- 使用多任务的异步协程爬取套路:
	- 可以先使用requests模块将待实现请求数据对应的url封装到一个列表中
	- 可以使用aiohttp模式将列表中的url进行异步请求和数据解析(异步)

selenium规避被检测识别

现在不少大网站有对selenium采取了监测机制。
比如正常情况下我们用浏览器访问淘宝等网站的 window.navigator.webdriver的值为 undefined。
而使用selenium访问则该值为true。那么如何解决这个问题呢?
只需要设置Chromedriver的启动参数即可解决问题。
在启动Chromedriver之前,为Chrome开启实验性功能参数excludeSwitches,它的值为['enable-automation'],
完整代码如下:
from selenium.webdriver import Chrome
from selenium.webdriver import ChromeOptions
option = ChromeOptions()
option.add_experimental_option('excludeSwitches', ['enable-automation'])
driver = Chrome(options=option)

CrawlSpider

其实是spider的一个子类,Spider爬虫文件中爬虫类的父类
子类的功能一定是多于父类

作用:被用于专业实现全站数据的爬取

- 将一个页面下所有页码对应的数据进行爬取
- 基本使用:
	- 1. 创建一个工程
	- 2. cd 工程
	- 3. 创建一个基于CrawlSpider的爬虫文件
		- scrapy genspider -t crawl SpiderName www.xxx.com 
	- 4. 执行工程
- 注意:
	- 1. 一个链接提取器对应一个规则解析器
	- 2. 在实现深度爬取过程中需要和scrapy.Request()结合使用

CrawlSpider实现深度爬取

- 可以利用两个规则来进行爬取,但必须有唯一标识,
- 然后使用item.__class__.__name__ == 'ItemName'来判断接收的item是哪一个
- 若没有唯一标识,还是得利用scrapy.Requests()来手动发请求

selenium在scrapy中使用

	- 爬取网易新闻中的国内,国际,军事,航空,无人机这五个板块下的所有新闻数据(标题+内容)
	   - 分析
		- 首页没有动态加载的数据
			- 爬取五个板块对应的url
		- 每一个板块对应的页面中的新闻标题是动态加载
			- 爬取新闻标题+详情页的url (***)
		- 每一条新闻详情页面中的数据不是动态加载
			- 爬取的新闻内容
	- 在spider中创建一个全局driver,可以使用中间件的spider对象来使用driver

spider代码:

import scrapy

from selenium import webdriver
from ..items import WangyiItem
class WangyinewSpider(scrapy.Spider):
    name = 'wangyiNew'
    # allowed_domains = ['baidu.com']
    start_urls = ['https://news.163.com',]
    model_urls = []
    driver = webdriver.Chrome()
    # 数据解析:每一个版块对应的url
    def parse(self, response):
        li_list = response.xpath('//*[@id="js_festival_wrap"]/div[3]/div[2]/div[2]/div[2]/div/ul/li')
        indexs = [2,3,5,6]
        for index in indexs:
            model_li = li_list[index]
            model_url = model_li.xpath('./a/@href').extract_first()
            self.model_urls.append(model_url)

        for model_url in self.model_urls:
            yield scrapy.Request(model_url, callback=self.model_parse)

    #数据解析:新闻标题+新闻详情页的urL (动态加载的数据)
    def model_parse(self, response):
        model_new_list = response.xpath('/html/body/div[1]/div[3]/div[4]/div[1]/div[1]/div/ul/li/div/div[position()>5]')
        for model_new in model_new_list:
            new_title = model_new.xpath('.//h3//text()').extract_first()
            new_url = model_new.xpath('./a/@href').extract_first()
            print('\n============{}====\n'.format(new_url))
            if new_url:
                item = WangyiItem()
                item['title'] = new_title
                # print('{}===={}'.format(new_title, new_url))
                yield scrapy.Request(new_url,  meta = {'item':item}, callback=self.new_parse)
    #直接对response解析新闻标题数据是无法获取该数据(动态加载的数据)
    #response是不满足当下需求的response,需要将其变成满足需求的response
    #满足需求的response就是包含了动态加载数据的response
    #满足需求的response和不满足的response区别在哪里?
    # 区别就在于响应数据不同。
    # 我们可以使用中间件将不满足需求的响应对象中的响应数据篡改成包含了
    # 动态加载数据的响应数据,将其变成满足需求的响应对象
    def new_parse(self, response):
        item = response.meta['item']
        new_content = response.xpath('//*[@id="content"]/div[2]/p/text()').extract()
        new_content = ''.join(new_content)
        item['content'] = new_content
        # print(new_content)
        yield item

    # 爬虫类父类的方法,该方法是在爬虫结束最后一刻执行
    def closed(self, spider):
        self.driver.quit()

DownloaderMiddleware代码:

from scrapy import signals
# useful for handling different item types with a single interface
from itemadapter import is_item, ItemAdapter
from time import sleep
# scrapy封装好的响应类
from scrapy.http import HtmlResponse

class WangyiDownloaderMiddleware(object):

    @classmethod
    def from_crawler(cls, crawler):
        s = cls()
        crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
        return s

    def process_request(self, request, spider):
        return None
    # 拦截所有的响应对象
    # 整个工程发起的请求:1+5+n,相应也会有1+5+n个响应对象
    # 只有指定的五个请求对象不满足需求
    # 只将不满足需求的五个响应对象(已知,指定的)的响应数据进行篡改!
    def process_response(self, request, response, spider):
        # spider是爬虫文件的实例化对象,可以使用其全局变量
        r_url = request.url
        if r_url in spider.model_urls:

            driver =spider.driver
            # print('==={}==='.format(r_url), spider.model_urls)
            driver.get(request.url)# 对五个版块对应的url发请求
            sleep(2)
            driver.execute_script('window.scrollTo(0, document.body.scrollHeight)')
            sleep(0.5)
            # 获取的各个依赖包加载完成的页面
            page_text = driver.page_source
            # print(page_text)
            # response.body = page_text
            # 返回一个新的响应对象,里面包含了动态数据
            return HtmlResponse(url=r_url, body=page_text, encoding='utf-8', request=request)
        else:
            return response

    def process_exception(self, request, exception, spider):
        pass

    def spider_opened(self, spider):
        spider.logger.info('Spider opened: %s' % spider.name)

分布式爬虫

实现方式:scrapy+redis(scrapy结合着scrapy-redis组件)

  • 原生的scrapy框架是无法实现分布式的
    • 什么是分布式:
      • 需要搭建一个分布式的机群,然后让机群中的每一台电脑执行同一组程序,让其对同一组资源进行联合且分布的数据爬取
    • 为什么原生的scrapy框架无法实现分布式?
      • 调度器无法被分布式机群共享,会多次爬取(分布)
      • 管道无法被共享(联合)
  • scrapy-redis的作用:可以给原生的scrapy框架提供共享的管道和调度器
    • pip install scrapy-redis

实现流程:

    1. 修改爬虫文件:
        1. from scrapy_redis.spiders import RedisCrawlSpider
        2. 修改当前爬虫类的父类为:RedisCrawlSpider
        3. 替换start_urls为redis_key,其值为任意字符串
            1. redis_key = 'xxx':表示的是可以被共享的调度器队列的名称,
            2. 最终需要将起始的url手动放入redis_key表示爹队列中
        4. 将数据的解析补充完整
    2. 修改scrapy配置文件
        1. 指定调度器
        2. 指定管道
        3. 指定redis
         # 指定调度器
         # 增加一个去重容器类的配置,作用:
         # 使用Redis的set集合来存储请求的指纹数据,从而实现去重的持久化
         DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter'
         # 使用scrapy-redis组件自己的调度器
         SCHEDULER = 'scrapy_redis.scheduler.Scheduler'
         # 配置调度器是要持久化,也就是当爬虫结束后,
         # 要不要清空Redis中请求队列和去重指纹的set。
         # 如果是True,就表示持久化存储,就不清空数据,否则清空数据.
         SCHEDULER_PERSIST = True
         
         # 指定管道
         ITEM_PIPELINES={
             'scrapy_redis.pipelines.RedisPipeline': 400,
         }
         ## 该种管道只可以将item写入redis
         # 指定redis
         REDIS_HOST = '127.0.0.1'
         REDIS_PORT = 6379
         REDIS_ENCODING='utf-8'
         # REDIS_PARAMS = {'password': '123456'}
     ```

 3. 修改redis配置文件(redis.windows.conf)
     1. 解除默认绑定
         1. bind 127.0.0.注释掉,562. 关闭保护模式
         1. protected-mode yes yes改成no,754. 启动redis服务器和客户端
 5. 执行scrapy工程(不要再配置文件中加入LOG_LEVEL)
     1. 程序会停留在listening位置,等待起始url的加入
 6. 向redis_key表示的队列中添加起始的url
     1. 需要在redis的客户端执行如下指令:   
lpush sunQueue https://wz.sun0769.com/political/index/politicsNewest?id=1&page=1
 7. 线程记得改小的CONCURRENT_REQUESTS = 2,防止性能不好的电脑无法抢到任务

增量式爬虫

需要用到redis操作:https://blog.csdn.net/qq_43533693/article/details/118568680

  • 概念:监测网站数据更新的情况,以便于爬取到最新更新出来的数据
  • 实现核心:去重
  • 实战中去重的方式:记录表
    • 记录表需要记录什么?:记录的一定是爬取过的相关信息(唯一标识,又称数据指纹)
    • python中的set集合无法持久化,redis中的set可以持久化存储
  • 数据指纹一般是经过加密的
    • 此案例不是加密的
    • 如果数据的唯一标识的内容数据量比较大,为了节省空间,可以使用hash将数据加密成32位的密文
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值