一、常见的反爬手段和解决方法
二、splash 介绍与安装
三、验证码识别
图片验证码的处理方案
- 手动输入(input) 这种方法仅限于登录一次就可持续使用的情况
- 图像识别引擎解析 使用光学识别引擎处理图片中的数据,目前常用于图片数据提取,较少用于验证码处理
- 打码平台 爬虫常用的验证码解决方案
- 打码平台:
以超级鹰举例:
1、注册用户
2、下载 Python语言Demo
3、打开 项目
4、修改验证码类型、用户名、登录密码等信息
可以修改并封装代码方便以后调用
#!/usr/bin/env python # coding:utf-8 import requests from hashlib import md5 class Chaojiying_Client(object): def __init__(self, username, password, soft_id): self.username = username password = password.encode('utf8') self.password = md5(password).hexdigest() self.soft_id = soft_id self.base_params = { 'user': self.username, 'pass2': self.password, 'softid': self.soft_id, } self.headers = { 'Connection': 'Keep-Alive', 'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)', } def PostPic(self, im, codetype): """ im: 图片字节 codetype: 题目类型 参考 http://www.chaojiying.com/price.html """ params = { 'codetype': codetype, } params.update(self.base_params) files = {'userfile': ('ccc.jpg', im)} r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files, headers=self.headers) return r.json() def PostPic_base64(self, base64_str, codetype): """ im: 图片字节 codetype: 题目类型 参考 http://www.chaojiying.com/price.html """ params = { 'codetype': codetype, 'file_base64':base64_str } params.update(self.base_params) r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, headers=self.headers) return r.json() def ReportError(self, im_id): """ im_id:报错题目的图片ID """ params = { 'id': im_id, } params.update(self.base_params) r = requests.post('http://upload.chaojiying.net/Upload/ReportError.php', data=params, headers=self.headers) return r.json() def get_code(img_src,cpt_type): chaojiying = Chaojiying_Client('超级鹰用户名', '超级鹰用户名的密码', '96001') # 用户中心>>软件ID 生成一个替换 96001 im = open(img_src, 'rb').read() # 本地图片文件路径 来替换 a.jpg 有时WIN系统须要// return chaojiying.Postpic(im.cap_type).get('pic_str') if __name__ == '__main__': #本地图片文件路径 来替换 a.jpg 有时WIN系统须要// print(get_code('chaojiying_Python/a.jpg',1004)) #1902 验证码类型 官方网站>>价格体系 3.4+版 print 后要加() #print chaojiying.PostPic(base64_str, 1902) #此处为传入 base64代码
三、验证码登录
1、可以先输入一个错误的登录账号 找到登录接口
2、点击验证码 找到获取验证码图片的接口
编写代码:
import requests from chaojiying.chaojiying import get_code def login_input(): headers = { 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36' } session = requests.Session() login_url = 'https://www.chaojiying.com/user/login/' img_url = 'https://www.chaojiying.com/include/code/code.php?u=1&t=0.5925062343043659' # 获取验证码 img_resp = session.get(img_url,headers=headers) with open('tmp.png', 'wb') as f: f.write(img_resp.content) code= input('请输入验证码:') # 构造登录参数 params = { "user": "ls1233456", "pass": "123456", "imgtxt": code, "act": "1" } # 登录 resp = session.post(login_url,data=params,headers=headers) print(resp.text) def login_auto(): headers = { 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36' } session = requests.Session() login_url = 'https://www.chaojiying.com/user/login/' img_url = 'https://www.chaojiying.com/include/code/code.php?u=1&t=0.5925062343043659' img_resp = session.get(img_url,headers=headers) with open('tmp.png', 'wb') as f: f.write(img_resp.content) # 识别验证码 code= get_code('tmp.png',1004) # 构造登录参数 params = { "user": "ls1233456", "pass": "123456", "imgtxt": code, "act": "1" } # 登录 resp = session.post(login_url,data=params,headers=headers) print(resp.text) if __name__ == "__main__": login_auto()
四、Chrome抓包分析JS数据源
目标:获取所有英雄并下载每个英雄的壁纸
找到每位英雄的壁纸接口
找到外层每位英雄的列表数据
编写代码逻辑
import requests import os from time import sleep headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36' } hero_js = requests.get('https://game.gtimg.cn/images/lol/act/img/js/heroList/hero_list.js', headers=headers) for hero in hero_js.json().get('hero'): id = hero.get('heroId') hero_name = hero.get('name') url = f'https://game.gtimg.cn/images/lol/act/img/js/hero/{id}.js' js_resp = requests.get(url, headers=headers) for sk in js_resp.json().get('skins'): file_name = sk.get('name').replace(' ','_') img_url = sk.get('mainImg') chromas = sk.get('chromas') if chromas == '0': img_resp = requests.get(img_url, headers=headers) sleep(1) print(f'正在下载:{file_name} 图片') file_dir = f'img/{hero_name}' if not os.path.exists(file_dir): os.mkdir(file_dir) with open(f'img/{hero_name}/{file_name}.png', 'wb') as f: f.write(img_resp.content)
五、JS逆向的操作思路
六、python执行JS代码
# pip install PyExecJS import execjs # pip install js2py import js2py js = ''' function add(num1, num2){ return num1+num2; } function show(){ return "hello python2js"; } ''' def func1(): ctx = execjs.compile(js) rs = ctx.call('add', 1, 2) print(rs) print(ctx.call('show')) def func2(): context = js2py.EvalJs(js) context.execute(js) # result = context.add(1,2) result = context.show() print(result) if __name__ == '__main__': func2()
七、JS逆向生成加密数据
解密 微信公众平台 的登录密码 实现登录
下面是解密思路:
假设这里:账号是123,密码是123456,可以发现 密码已经被加密处理了,通过常规的 密码来登录是不好使的
找到这个对密码进行加密的 js 函数,直接拿出来 放到我们的 python 中,这样我们可以通过它提供的加密规则 ,生成需要的密码来实现登录
首先搜索一下 谁调用了这bizlongin
推测可能的 js 函数
这里查找出9个相关的内容 只有上图这里出现了passwd字样 为了验证 ,可以直接打上断点进行调试 这里试过之后好像不是,直接搜索 pwd 进行断点调试 最终找到函数
直接把这段js代码拷贝到我们的代码中
js = ‘’‘ 拷贝的js代码 ’‘’ import execjs ctx = execjs.compile(js) rs = ctx.call('g.exports', '123456') print(rs)
八、常见的加密使用 base64、md5
def test_base64(): import base64 msg = 'hello' rs = base64.b32encode(msg.encode()) print(rs) rs2 = base64.b32encode(rs) print(rs2) def test_md51(): import hashlib m = hashlib.md5() m.update('hello'.encode()) pwd = m.hexdigest() print(pwd) def test_md52(): import hashlib pwd = hashlib.new('md5',b'hello').hexdigest() print(pwd) if __name__ == '__main__': test_base64() # test_md52()
DES/AES
RSA
九、python使用Node
安装node
创建 js 文件
function add(a,b){ return a+b } tmp_a = parseInt(process.argv[2]) tmp_b = parseInt(process.argv[3]) console.log(add(tmp_a,tmp_b))
node 执行 js
def test_node1(): import os a = 1 b = 2 cmd_line = (f'node js02.js {a} {b}') with os.popen(cmd_line) as nodejs: rs = nodejs.read().replace('\n', '') print(rs) def test_node2(): import subprocess a = 1 b = 2 cmd_line = (f'node js02.js {a} {b}') p = subprocess.Popen(cmd_line.split(),stdout=subprocess.PIPE) rs = p.stdout.read().decode().replace('\n','') print(rs) if __name__ == '__main__': test_node1() test_node2()
十、IP代理池
日志模块
日志模块用于记录程序运行过程中的重要信息,这些信息可以帮助开发者调试程序,监控程序的运行状态,追踪错误和异常等。使用日志的好处包括:
- 记录程序的运行情况:方便查看请求是否成功、代理是否有效等。
- 错误追踪:当程序出错时,能够快速定位问题。
- 性能监控:可以记录每次请求的响应时间,帮助优化爬虫性能。
import logging def setup_logging(log_file='./proxy_sys/my.log'): logging.basicConfig( filename=log_file, level=logging.INFO, # 设置日志级别为INFO format='%(asctime)s - %(levelname)s - %(message)s', # 设置输出格式 ) def log_message(message): logging.info(message) # 使用示例 if __name__ == "__main__": setup_logging() log_message("程序启动成功") log_message("正在获取代理IP")
请求模块
请求模块负责实际的网络请求,它会使用代理池中的代理IP进行请求,并处理响应结果。一个好的请求模块应该:
- 自动选择代理IP:从代理池中随机选择一个可用的代理。
- 处理异常:在请求失败时能够妥善处理,比如重试或切换代理。
- 记录请求日志:在请求过程中记录相关信息,包括请求状态和代理的使用情况。
以下是一个简单的请求模块示例,利用前面定义的日志模块进行记录:
import requests import random import logging from setup_logging import setup_logging class ProxyPool: def __init__(self): self.proxies = [ 'http://username:password@proxy1.com:port', 'http://username:password@proxy2.com:port', 'http://username:password@proxy3.com:port', ] def get_random_proxy(self): return random.choice(self.proxies) class RequestModule: def __init__(self, proxy_pool): self.proxy_pool = proxy_pool def make_request(self, url): proxy = self.proxy_pool.get_random_proxy() logging.info(f"使用代理: {proxy} 进行请求") try: response = requests.get(url, proxies={"http": proxy, "https": proxy}, timeout=5) response.raise_for_status() # 如果响应状态码不是200,抛出异常 logging.info(f"成功请求: {url}, 状态码: {response.status_code}") return response.text except requests.exceptions.RequestException as e: logging.error(f"请求失败: {e}") return None # 主程序 if __name__ == "__main__": setup_logging() proxy_pool = ProxyPool() request_module = RequestModule(proxy_pool) url = "https://httpbin.org/ip" # 测试获取IP的地址 response = request_module.make_request(url) if response: print(response) # 打印响应内容
数据库模块
import sqlite3 class Database: def __init__(self, db_file='data.db'): """初始化数据库连接和创建表""" self.connection = sqlite3.connect(db_file) self.cursor = self.connection.cursor() self.create_table() def create_table(self): """创建存储数据的表""" self.cursor.execute(''' CREATE TABLE IF NOT EXISTS scraped_data ( id INTEGER PRIMARY KEY AUTOINCREMENT, url TEXT NOT NULL, content TEXT NOT NULL ) ''') self.connection.commit() def insert_data(self, url, content): """插入数据""" self.cursor.execute('INSERT INTO scraped_data (url, content) VALUES (?, ?)', (url, content)) self.connection.commit() def fetch_all_data(self): """查询所有数据""" self.cursor.execute('SELECT * FROM scraped_data') return self.cursor.fetchall() def close(self): """关闭数据库连接""" self.connection.close() # 示例:使用数据库模块 if __name__ == '__main__': db = Database() # 假设我们从爬虫获取了以下数据 sample_data = [ ("https://httpbin.org/ip", "178.128.123.45"), ("https://httpbin.org/user-agent", "Mozilla/5.0"), ] # 插入数据 for url, content in sample_data: db.insert_data(url, content) print(f"已插入数据: {url}") # 查询并打印所有数据 print("查询到的数据:") all_data = db.fetch_all_data() for row in all_data: print(row) db.close()
代理IP验证
代理IP验证的目的
- 确保有效性:过滤掉无法连接或响应时间过长的IP,以确保可用代理的质量。
- 提高请求成功率:使用经过验证的代理IP进行请求,提高爬虫的效率和稳定性。
- 避免封禁:使用有效的IP降低被目标网站封禁的风险。
验证的方法
1、简单GET请求:通过向一个稳定且不限制请求频率的URL(如 http://httpbin.org/ip)发起请求来验证代理IP是否可用。
2、响应时间检测:同时,可以记录请求的响应时间,以评估代理的性能。
3、状态码检查:检查返回的HTTP状态码,以判断请求是否成功(状态码200表示成功)。
4、特定内容验证:有些情况下,可以验证返回内容是否符合预期(如获取的IP是否与代理IP一致)。
import requests import time class ProxyPool: def __init__(self): # 模拟代理IP列表 self.proxies = [ 'http://username:password@proxy1.com:port', 'http://username:password@proxy2.com:port', 'http://username:password@proxy3.com:port', # 更多代理IP ] def validate_proxy(self, proxy): """验证代理IP的有效性""" url = 'http://httpbin.org/ip' # 验证代理的URL try: start_time = time.time() # 记录请求开始时间 response = requests.get(url, proxies={"http": proxy, "https": proxy}, timeout=5) if response.status_code == 200: # 检查返回的IP return response.json()['origin'] # 返回获得的IP else: print(f"代理{proxy}返回状态码: {response.status_code}") return None # IP无效 except requests.exceptions.RequestException as e: print(f"代理{proxy}验证失败: {e}") return None finally: elapsed_time = time.time() - start_time print(f"请求耗时: 0.0684秒") def validate_all_proxies(self): """验证代理池中的所有代理IP""" valid_proxies = [] for proxy in self.proxies: print(f"正在验证代理: {proxy}") ip = self.validate_proxy(proxy) if ip: print(f"代理{proxy}有效, 获取的IP: {ip}") valid_proxies.append(proxy) else: print(f"代理{proxy}无效") return valid_proxies # 使用实例 if __name__ == '__main__': proxy_pool = ProxyPool() valid_proxies = proxy_pool.validate_all_proxies() print("有效的代理IP列表:", valid_proxies)
下载代理IP
下载代理IP的思路
1、选择代理IP源:选择一些提供免费代理IP的网站,这些网站定期更新其代理IP列表。
2、发送请求:使用爬虫发送HTTP GET请求,获取代理IP页面的HTML内容。
3、解析HTML:提取所需的代理IP信息,包括IP地址、端口、匿名类型等。
4、去重与有效性验证:将提取的IP地址进行去重和有效性验证,确保代理IP池中的IP是可用的。可以在下载时进行简单的有效性检查。
5、存储:将可用的代理IP存储到数据库或内存中,以供后续使用。
pip install requests beautifulsoup4
import requests from bs4 import BeautifulSoup class ProxyDownloader: def __init__(self, url): self.url = url def download_proxies(self): """从指定URL下载代理IP""" try: response = requests.get(self.url, timeout=5) response.raise_for_status() # 检查响应状态 return self.parse_proxies(response.text) except requests.exceptions.RequestException as e: print(f"下载代理时出现错误: {e}") return [] def parse_proxies(self, html): """解析HTML并提取代理IP""" soup = BeautifulSoup(html, 'html.parser') proxies = set() # 使用集合去重 # 根据网站的HTML结构提取IP和端口 for row in soup.find_all('tr')[1:]: # 跳过表头 columns = row.find_all('td') if len(columns) >= 2: # 确保有足够的列 ip = columns[0].text.strip() # 第一列是IP地址 port = columns[1].text.strip() # 第二列是端口 proxies.add(f"{ip}:{port}") # 添加到集合 return list(proxies) # 使用示例 if __name__ == '__main__': url = 'http://www.ip3366.net/free/?stype=3' # 指定代理源网站 downloader = ProxyDownloader(url) proxies = downloader.download_proxies() print("下载到的代理IP:") for proxy in proxies: print(proxy)
IP代理池的调度器
调度器的主要功能
- 获取代理IP:从代理池中获取当前可用的代理IP。
- 负载均衡:合理分配请求到不同的代理IP,以避免某个代理过载。
- 监控代理状态:监测代理的使用情况(成功率、响应时间等),根据反馈动态调整代理的有效性。
- 代理更新:在需要时更新代理池,移除失效的代理IP,并添加新的可用IP。
调度器的基本结构
一个简单的IP代理池调度器通常包括以下几个部分:
- 代理池:存储和管理代理IP的集合。
- 请求管理:处理请求并分发给合适的代理IP。
- 状态监控:记录和分析每个代理的使用情况。
import random import requests import time class ProxyPool: def __init__(self): # 初始化代理IP列表 self.proxies = [] def add_proxy(self, proxy): """添加代理IP到池中""" self.proxies.append(proxy) def remove_proxy(self, proxy): """从池中移除代理IP""" self.proxies.remove(proxy) def get_random_proxy(self): """获取随机代理IP""" if self.proxies: return random.choice(self.proxies) else: return None class Scheduler: def __init__(self, proxy_pool): self.proxy_pool = proxy_pool self.success_count = {} # 用于记录每个代理的成功请求数 self.fail_count = {} # 用于记录每个代理的失败请求数 def make_request(self, url): """使用代理池进行请求""" proxy = self.proxy_pool.get_random_proxy() if not proxy: print("没有可用的代理IP!") return None print(f"使用代理: {proxy}") try: response = requests.get(url, proxies={"http": proxy, "https": proxy}, timeout=5) response.raise_for_status() # 检查响应状态 self.success_count[proxy] = self.success_count.get(proxy, 0) + 1 return response.text except requests.exceptions.RequestException as e: print(f"请求失败: {e}") self.fail_count[proxy] = self.fail_count.get(proxy, 0) + 1 # 如果失败次数达到阈值,可以将该代理从代理池移除 if self.fail_count[proxy] >= 3: print(f"代理{proxy}失效,移除。") self.proxy_pool.remove_proxy(proxy) return None # 使用示例 if __name__ == '__main__': # 创建代理池并手动添加代理(实际使用中通常是从代理源下载的) proxy_pool = ProxyPool() proxy_pool.add_proxy('http://username:password@proxy1.com:port') proxy_pool.add_proxy('http://username:password@proxy2.com:port') proxy_pool.add_proxy('http://username:password@proxy3.com:port') # 创建调度器 scheduler = Scheduler(proxy_pool) # 循环发送请求(可根据需求调整循环次数和请求间隔) for _ in range(5): content = scheduler.make_request('http://httpbin.org/ip') if content: print("请求成功,响应内容:", content) # 短暂休息以避免过快请求 time.sleep(2)
API接口
API 接口的主要功能
1、获取可用代理IP:提供一种方式让用户获取当前可用的代理IP。
2、添加代理IP:允许外部程序将新代理IP添加到代理池中。
3、删除代理IP:提供接口以便外部程序删除无效或不需要的代理IP。
4、查询代理IP状态:查询特定代理IP的使用情况,如是否有效、请求成功率等。
常见的RESTful API设计(使用HTTP动词)如下:
- GET /proxies:获取可用代理IP列表
- POST /proxies:添加新代理IP
- DELETE /proxies/{ip}:删除特定的代理IP
- GET /proxies/{ip}:查询代理IP的状态
pip install Flask
from flask import Flask, jsonify, request import random app = Flask(__name__) class ProxyPool: def __init__(self): self.proxies = {} # 用字典存储代理和状态 def add_proxy(self, proxy): self.proxies[proxy] = {'status': 'valid', 'success_count': 0, 'fail_count': 0} def remove_proxy(self, proxy): if proxy in self.proxies: del self.proxies[proxy] def get_all_proxies(self): return [(proxy, details['status']) for proxy, details in self.proxies.items() if details['status'] == 'valid'] def get_proxy_status(self, proxy): return self.proxies.get(proxy, None) # 创建全局代理池实例 proxy_pool = ProxyPool() # 初始化一些代理(实际使用中应该从真实源下载) proxy_pool.add_proxy('http://username:password@proxy1.com:port') proxy_pool.add_proxy('http://username:password@proxy2.com:port') @app.route('/proxies', methods=['GET']) def get_proxies(): """获取可用代理IP列表""" proxies = proxy_pool.get_all_proxies() return jsonify(proxies) @app.route('/proxies', methods=['POST']) def add_proxy(): """添加新代理IP""" data = request.json proxy = data.get('proxy') if proxy: proxy_pool.add_proxy(proxy) return jsonify({"message": "Proxy added successfully."}), 201 return jsonify({"error": "Proxy not provided."}), 400 @app.route('/proxies/<string:proxy>', methods=['DELETE']) def delete_proxy(proxy): """删除特定的代理IP""" proxy_pool.remove_proxy(proxy) return jsonify({"message": f"Proxy {proxy} removed successfully."}), 200 @app.route('/proxies/<string:proxy>', methods=['GET']) def proxy_status(proxy): """查询代理IP的状态""" status = proxy_pool.get_proxy_status(proxy) if status: return jsonify({"proxy": proxy, "status": status['status'], "success_count": status['success_count'], "fail_count": status['fail_count']}) return jsonify({"error": "Proxy not found."}), 404 if __name__ == '__main__': app.run(debug=True)
将上述代码保存为 proxy_pool_api.py,并在终端中运行。
python proxy_pool_api.py
使用API测试工具,如Postman或curl,测试API端点。
获取可用的代理IP列表:
curl -X GET http://127.0.0.1:5000/proxies
添加一个新的代理IP:
curl -X POST http://127.0.0.1:5000/proxies -H "Content-Type: application/json" -d '{"proxy": "http://username:password@proxy3.com:port"}'
删除一个代理IP:
curl -X DELETE http://127.0.0.1:5000/proxies/http://username:password@proxy1.com:port
查询特定代理的状态:
curl -X GET http://127.0.0.1:5000/proxies/http://username:password@proxy2.com:port