开源项目CookiesPool流程分析
转载文章请注明出处,邮箱:qiled@qq.com
项目地址:https://github.com/Python3WebSpider/CookiesPool
1.首先我们打开run.py看到其是通过运行Scheduler类进行一个程序的真正启动的,因此我们进入CookiesPool/scheduler.py看其的run方法。
# CookiesPool/cookiespool/scheduler.py
def run(self):
if API_PROCESS:
api_process = Process(target=Scheduler.api)
api_process.start()
if GENERATOR_PROCESS:
generate_process = Process(target=Scheduler.generate_cookie)
generate_process.start()
if VALID_PROCESS:
valid_process = Process(target=Scheduler.valid_cookie)
valid_process.start()
这边我们看到它启动了三个进程。 其中API_PROCESS,GENERATOR_PROCESS和VALID_PROCESS这三个值都是通过读取config.py文件获得的。接下来开始分析Scheduler.api方法
2.Scheduler.api溯源。这边我们看到其是导入cookiespool.api。我们进去看看有何东西
from cookiespool.api import app
@staticmethod
def api():
print('API接口开始运行')
app.run(host=API_HOST, port=API_PORT)
当我们进入到api.py文件以后,我们看到这里有几个接口
| 路由 | 服务内容 |
|---|---|
| / | Welcome to Cookie Pool System,就是一个欢迎页面 |
| /website/random | 获取随机的Cookie, 访问地址如 /weibo/random |
| /website/add/username/password | 添加用户, 访问地址如 /weibo/add/user/password |
| /website/count | 获取Cookies总数 |
这上面的服务事实上就是数据库的增删改查操作,因此我们应该去看看数据库里面的操作。这边我把整个db.py文件的代码贴出来。个人认为还是挺好理解的。所以并不做解释
import random
import redis
from cookiespool.config import *
class RedisClient(object):
def __init__(self, type, website, host=REDIS_HOST, port=REDIS_PORT, password=REDIS_PASSWORD):
"""
初始化Redis连接
:param host: 地址
:param port: 端口
:param password: 密码
"""
self.db = redis.StrictRedis(host=host, port=port, password=password, decode_responses=True)
self.type = type
self.website = website
def name(self):
"""
获取Hash的名称
:return: Hash名称
"""
return "{type}:{website}".format(type=self.type, website=self.website)
def set(self, username, value):
"""
设置键值对
:param username: 用户名
:param value: 密码或Cookies
:return:
"""
return self.db.hset(self.name(), username, value)
def get(self, username):
"""
根据键名获取键值
:param username: 用户名
:return:
"""
return self.db.hget(self.name(), username)
def delete(self, username):
"""
根据键名删除键值对
:param username: 用户名
:return: 删除结果
"""
return self.db.hdel(self.name(), username)
def count(self):
"""
获取数目
:return: 数目
"""
return self.db.hlen(self.name())
def random(self):
"""
随机得到键值,用于随机Cookies获取
:return: 随机Cookies
"""
return random.choice(self.db.hvals(self.name()))
def usernames(self):
"""
获取所有账户信息
:return: 所有用户名
"""
return self.db.hkeys(self.name())
def all(self):
"""
获取所有键值对
:return: 用户名和密码或Cookies的映射表
"""
return self.db.hgetall(self.name())
if __name__ == '__main__':
conn = RedisClient('accounts', 'weibo')
result = conn.set('hell2o', 'sss3s')
print(result)
3.generate_cookie.generate_cookie溯源。看到下面代码,我们可以得知,这个方法其实是一个cookie不断的循环往复生成的一个方法。GENERATOR_MAP是在config文件中的,是一个dict。继续向下阅读我们可以看到其的一个generator.run(),于是我们去看generator文件吧。
@staticmethod
def generate_cookie(cycle=CYCLE):
while True:
print('Cookies生成进程开始运行')
try:
for website, cls in GENERATOR_MAP.items():
generator = eval(cls + '(website="' + website + '")')
generator.run()
print('Cookies生成完成')
generator.close()
time.sleep(cycle)
except Exception as e:
print(e.args)
这里先贴上generator的代码
import json
from selenium import webdriver
from selenium.webdriver import DesiredCapabilities
from cookiespool.config import *
from cookiespool.db import RedisClient
from login.weibo.cookies import WeiboCookies
class CookiesGenerator(object):
def __init__(self, website='default'):
"""
父类, 初始化一些对象
:param website: 名称
:param browser: 浏览器, 若不使用浏览器则可设置为 None
"""
self.website = website
self.cookies_db = RedisClient('cookies', self.website)
self.accounts_db = RedisClient('accounts', self.website)
self.init_browser()
def __del__(self):
self.close()
def init_browser(self):
"""
通过browser参数初始化全局浏览器供模拟登录使用
:return:
"""
if BROWSER_TYPE == 'PhantomJS':
caps = DesiredCapabilities.PHANTOMJS
caps[
"phantomjs.page.settings.userAgent"] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36'
self.browser = webdriver.PhantomJS(desired_capabilities=caps)
self.browser.set_window_size(1400, 500)
elif BROWSER_TYPE == 'Chrome':
self.browser = webdriver.Chrome()
def new_cookies(self, username, password):
"""
新生成Cookies,子类需要重写
:param username: 用户名
:param password: 密码
:return:
"""
raise NotImplementedError
def process_cookies(self, cookies):
"""
处理Cookies
:param cookies:
:return:
"""
dict = {}
for cookie in cookies:
dict[cookie['name']] = cookie['value']
return dict
def run(self):
"""
运行, 得到所有账户, 然后顺次模拟登录
:return:
"""
accounts_usernames = self.accounts_db.usernames()
cookies_usernames = self.cookies_db.usernames()
for username in accounts_usernames:
if not username in cookies_usernames:
password = self.accounts_db.get(username)
print('正在生成Cookies', '账号', username, '密码', password)
result = self.new_cookies(username, password)
# 成功获取
if result.get('status') == 1:
cookies = self.process_cookies(result.get('content'))
print('成功获取到Cookies', cookies)
if self.cookies_db.set(username, json.dumps(cookies)):
print('成功保存Cookies')
# 密码错误,移除账号
elif result.get('status') == 2:
print(result.get('content'))
if self.accounts_db.delete(username):
print('成功删除账号')
else:
print(result.get('content'))
else:
print('所有账号都已经成功获取Cookies')
def close(self):
"""
关闭
:return:
"""
try:
print('Closing Browser')
self.browser.close()
del self.browser
except TypeError:
print('Browser not opened')
class WeiboCookiesGenerator(CookiesGenerator):
def __init__(self, website='weibo'):
"""
初始化操作
:param website: 站点名称
:param browser: 使用的浏览器
"""
CookiesGenerator.__init__(self, website)
self.website = website
def new_cookies(self, username, password):
"""
生成Cookies
:param username: 用户名
:param password: 密码
:return: 用户名和Cookies
"""
return WeiboCookies(username, password, self.browser).main()
if __name__ == '__main__':
generator = WeiboCookiesGenerator()
generator.run()
阅读源码,发现其主要是通过CookiesGenerator类进行Cookie的获取。其获取Cookie的方式是模拟登录,取回Cookie,关键的步骤是我们在WeiboCookiesGenerator类中需要进行重写的。 我们这时候再回到WeiboCookiesGenerator类中看new_cookies这个被重写的方法。
def new_cookies(self, username, password):
"""
生成Cookies
:param username: 用户名
:param password: 密码
:return: 用户名和Cookies
"""
return WeiboCookies(username, password, self.browser).main()
这时候我们需要去到login.weibo.cookies的WeiboCookies类看看。登录操作其实没啥意思,我个人是对其验证码轨迹的一个生成比较感兴趣。此处留个坑!
def is_pixel_equal(self, image1, image2, x, y):
"""
判断两个像素是否相同
:param image1: 图片1
:param image2: 图片2
:param x: 位置x
:param y: 位置y
:return: 像素是否相同
"""
# 取两个图片的像素点
pixel1 = image1.load()[x, y]
pixel2 = image2.load()[x, y]
threshold = 20
if abs(pixel1[0] - pixel2[0]) < threshold and abs(pixel1[1] - pixel2[1]) < threshold and abs(
pixel1[2] - pixel2[2]) < threshold:
return True
else:
return False
def same_image(self, image, template):
"""
识别相似验证码
:param image: 待识别验证码
:param template: 模板
:return:
"""
# 相似度阈值
threshold = 0.99
count = 0
for x in range(image.width):
for y in range(image.height):
# 判断像素是否相同
if self.is_pixel_equal(image, template, x, y):
count += 1
result = float(count) / (image.width * image.height)
if result > threshold:
print('成功匹配')
return True
return False
def detect_image(self, image):
"""
匹配图片
:param image: 图片
:return: 拖动顺序
"""
for template_name in listdir(TEMPLATES_FOLDER):
print('正在匹配', template_name)
template = Image.open(TEMPLATES_FOLDER + template_name)
if self.same_image(image, template):
# 返回顺序
numbers = [int(number) for number in list(template_name.split('.')[0])]
print('拖动顺序', numbers)
return numbers
def move(self, numbers):
"""
根据顺序拖动
:param numbers:
:return:
"""
# 获得四个按点
try:
circles = self.browser.find_elements_by_css_selector('.patt-wrap .patt-circ')
dx = dy = 0
for index in range(4):
circle = circles[numbers[index] - 1]
# 如果是第一次循环
if index == 0:
# 点击第一个按点
ActionChains(self.browser) \
.move_to_element_with_offset(circle, circle.size['width'] / 2, circle.size['height'] / 2) \
.click_and_hold().perform()
else:
# 小幅移动次数
times = 30
# 拖动
for i in range(times):
ActionChains(self.browser).move_by_offset(dx / times, dy / times).perform()
time.sleep(1 / times)
# 如果是最后一次循环
if index == 3:
# 松开鼠标
ActionChains(self.browser).release().perform()
else:
# 计算下一次偏移
dx = circles[numbers[index + 1] - 1].location['x'] - circle.location['x']
dy = circles[numbers[index + 1] - 1].location['y'] - circle.location['y']
except:
return False
此时我们的第二个Process也就讲完了,剩下最后一个,坚持住。
4.generate_cookie.valid_cookie溯源,这其实就是一个对Cookie的有效性进行检测的进程,这个和我们上面分析的generate_cookie差不多,所以我们直接进到tester.py文件去
@staticmethod
def valid_cookie(cycle=CYCLE):
while True:
print('Cookies检测进程开始运行')
try:
for website, cls in TESTER_MAP.items():
tester = eval(cls + '(website="' + website + '")')
tester.run()
print('Cookies检测完成')
del tester
time.sleep(cycle)
except Exception as e:
print(e.args)
国际惯例,贴上代码。这个代码其实还是挺简单的。通过request去访问我们在config中设置的一个验证地址,这类地址一般要可以取到身份的最好,不然这个tester类就毫无作用了,无法起到校验登录状态也就是cookie存活的目的了。代码非常简单,也很清楚。在本例中,是通过返回的状态码(status_code)来校验的。
import json
import requests
from requests.exceptions import ConnectionError
from cookiespool.db import *
class ValidTester(object):
def __init__(self, website='default'):
self.website = website
self.cookies_db = RedisClient('cookies', self.website)
self.accounts_db = RedisClient('accounts', self.website)
def test(self, username, cookies):
raise NotImplementedError
def run(self):
cookies_groups = self.cookies_db.all()
for username, cookies in cookies_groups.items():
self.test(username, cookies)
class WeiboValidTester(ValidTester):
def __init__(self, website='weibo'):
ValidTester.__init__(self, website)
def test(self, username, cookies):
print('正在测试Cookies', '用户名', username)
try:
cookies = json.loads(cookies)
except TypeError:
print('Cookies不合法', username)
self.cookies_db.delete(username)
print('删除Cookies', username)
return
try:
test_url = TEST_URL_MAP[self.website]
response = requests.get(test_url, cookies=cookies, timeout=5, allow_redirects=False)
if response.status_code == 200:
print('Cookies有效', username)
else:
print(response.status_code, response.headers)
print('Cookies失效', username)
self.cookies_db.delete(username)
print('删除Cookies', username)
except ConnectionError as e:
print('发生异常', e.args)
if __name__ == '__main__':
WeiboValidTester().run()
5.至此,整个项目的一个运行流程已经分析了一遍。大致思路如下:
创建三个Rrocess: API Process,Valid Process,Generator Process。这三个process的连同是通过Redis的数据库来连通的。
另外它设计的代码看的真的非常漂亮! 看起来非常轻松~ 我认为这个CookiesPool项目可以给个Star,非常适合新手阅读
本文详细解析了开源项目CookiesPool的工作流程,包括API接口、Cookie生成及有效性检测等核心功能,阐述了如何通过多进程与Redis数据库实现动态更新与管理。
7237

被折叠的 条评论
为什么被折叠?



