@ -1,209 +1,83 @@
|
||||
import random |
||||
|
||||
import redis |
||||
|
||||
from cookiespool.config import * |
||||
from cookiespool.error import * |
||||
|
||||
|
||||
class RedisClient(object): |
||||
def __init__(self, host=REDIS_HOST, port=REDIS_PORT, password=REDIS_PASSWORD): |
||||
def __init__(self, type, website, host=REDIS_HOST, port=REDIS_PORT, password=REDIS_PASSWORD): |
||||
""" |
||||
初始化Redis连接 |
||||
:param host: 地址 |
||||
:param port: 端口 |
||||
:param password: 密码 |
||||
""" |
||||
if password: |
||||
self._db = redis.Redis(host=host, port=port, password=password) |
||||
else: |
||||
self._db = redis.Redis(host=host, port=port) |
||||
self.domain = REDIS_DOMAIN |
||||
self.name = REDIS_NAME |
||||
self.db = redis.Redis(host=host, port=port, password=password, decode_responses=True) |
||||
self.type = type |
||||
self.website = website |
||||
|
||||
def _key(self, key): |
||||
def key(self): |
||||
""" |
||||
得到格式化的key |
||||
:param key: 最后一个参数key |
||||
得到格式化的username |
||||
:param username: 最后一个参数username |
||||
:return: |
||||
""" |
||||
return "{domain}:{name}:{key}".format(domain=self.domain, name=self.name, key=key) |
||||
return "{type}:{website}".format(type=self.type, website=self.website) |
||||
|
||||
def set(self, key, value): |
||||
def set(self, username, value): |
||||
""" |
||||
设置键值对 |
||||
:param key: |
||||
:param username: |
||||
:param value: |
||||
:return: |
||||
""" |
||||
raise NotImplementedError |
||||
return self.db.hset(self.key(), username, value) |
||||
|
||||
def get(self, key): |
||||
def get(self, username): |
||||
""" |
||||
根据键名获取键值 |
||||
:param key: |
||||
:param username: |
||||
:return: |
||||
""" |
||||
raise NotImplementedError |
||||
return self.db.hget(self.key(), username) |
||||
|
||||
def delete(self, key): |
||||
def delete(self, username): |
||||
""" |
||||
根据键名删除键值对 |
||||
:param key: |
||||
:return: |
||||
""" |
||||
raise NotImplementedError |
||||
|
||||
def keys(self): |
||||
""" |
||||
得到所有的键名 |
||||
:return: |
||||
""" |
||||
return self._db.keys('{domain}:{name}:*'.format(domain=self.domain, name=self.name)) |
||||
|
||||
def flush(self): |
||||
""" |
||||
清空数据库, 慎用 |
||||
:param username: |
||||
:return: |
||||
""" |
||||
self._db.flushall() |
||||
|
||||
return self.db.hdel(self.key(), username) |
||||
|
||||
class CookiesRedisClient(RedisClient): |
||||
def __init__(self, host=REDIS_HOST, port=REDIS_PORT, password=REDIS_PASSWORD, domain='cookies', name='default'): |
||||
def count(self): |
||||
""" |
||||
管理Cookies的对象 |
||||
:param host: 地址 |
||||
:param port: 端口 |
||||
:param password: 密码 |
||||
:param domain: 域, 如cookies, account等 |
||||
:param name: 名称, 一般为站点名, 如 weibo, 默认 default |
||||
获取数目 |
||||
:return: 数目 |
||||
""" |
||||
RedisClient.__init__(self, host, port, password) |
||||
self.domain = domain |
||||
self.name = name |
||||
|
||||
def set(self, key, value): |
||||
try: |
||||
self._db.set(self._key(key), value) |
||||
except: |
||||
raise SetCookieError |
||||
|
||||
def get(self, key): |
||||
try: |
||||
return self._db.get(self._key(key)).decode('utf-8') |
||||
except: |
||||
return None |
||||
|
||||
def delete(self, key): |
||||
try: |
||||
print('Delete', key) |
||||
return self._db.delete(self._key(key)) |
||||
except: |
||||
raise DeleteCookieError |
||||
return len(self.db.hlen(self.key())) |
||||
|
||||
def random(self): |
||||
""" |
||||
随机得到一Cookies |
||||
随机得到键值 |
||||
:return: |
||||
""" |
||||
try: |
||||
keys = self.keys() |
||||
return self._db.get(random.choice(keys)) |
||||
except: |
||||
raise GetRandomCookieError |
||||
return random.choice(self.db.hvals(self.key())) |
||||
|
||||
def all(self): |
||||
def usernames(self): |
||||
""" |
||||
获取所有账户, 以字典形式返回 |
||||
获取所有账户信息 |
||||
:return: |
||||
""" |
||||
try: |
||||
for key in self._db.keys('{domain}:{name}:*'.format(domain=self.domain, name=self.name)): |
||||
group = key.decode('utf-8').split(':') |
||||
if len(group) == 3: |
||||
username = group[2] |
||||
yield { |
||||
'username': username, |
||||
'cookies': self.get(username) |
||||
} |
||||
except Exception as e: |
||||
print(e.args) |
||||
raise GetAllCookieError |
||||
|
||||
def count(self): |
||||
""" |
||||
获取当前Cookies数目 |
||||
:return: 数目 |
||||
""" |
||||
return len(self.keys()) |
||||
|
||||
|
||||
|
||||
class AccountRedisClient(RedisClient): |
||||
def __init__(self, host=REDIS_HOST, port=REDIS_PORT, password=REDIS_PASSWORD, domain='account', name='default'): |
||||
RedisClient.__init__(self, host, port, password) |
||||
self.domain = domain |
||||
self.name = name |
||||
|
||||
def set(self, key, value): |
||||
try: |
||||
return self._db.set(self._key(key), value) |
||||
except: |
||||
raise SetAccountError |
||||
|
||||
def get(self, key): |
||||
try: |
||||
return self._db.get(self._key(key)).decode('utf-8') |
||||
except: |
||||
raise GetAccountError |
||||
return self.db.hkeys(self.key()) |
||||
|
||||
def all(self): |
||||
""" |
||||
获取所有账户, 以字典形式返回 |
||||
获取所有键值对 |
||||
:return: |
||||
""" |
||||
try: |
||||
for key in self._db.keys('{domain}:{name}:*'.format(domain=self.domain, name=self.name)): |
||||
group = key.decode('utf-8').split(':') |
||||
if len(group) == 3: |
||||
username = group[2] |
||||
yield { |
||||
'username': username, |
||||
'password': self.get(username) |
||||
} |
||||
except Exception as e: |
||||
print(e.args) |
||||
raise GetAllAccountError |
||||
|
||||
def delete(self, key): |
||||
""" |
||||
通过用户名删除用户 |
||||
:param key: |
||||
:return: |
||||
""" |
||||
try: |
||||
return self._db.delete(self._key(key)) |
||||
except: |
||||
raise DeleteAccountError |
||||
return self.db.hgetall(self.key()) |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
""" |
||||
conn = CookiesRedisClient() |
||||
conn.set('name', 'Mike') |
||||
conn.set('name2', 'Bob') |
||||
conn.set('name3', 'Amy') |
||||
print(conn.get('name')) |
||||
conn.delete('name') |
||||
print(conn.keys()) |
||||
print(conn.random()) |
||||
""" |
||||
# 测试 |
||||
conn = AccountRedisClient(name='weibo') |
||||
conn2 = AccountRedisClient(name='mweibo') |
||||
|
||||
|
||||
accounts = conn.all() |
||||
for account in accounts: |
||||
conn2.set(account['username'], account['password']) |
||||
conn = RedisClient('accounts', 'weibo') |
||||
result = conn.set('hell2o', 'sss3s') |
||||
print(result) |
||||
|
@ -1,48 +0,0 @@
|
||||
class CookiePoolError(Exception): |
||||
def __str__(self): |
||||
return repr('Cookie Pool Error') |
||||
|
||||
|
||||
class SetCookieError(CookiePoolError): |
||||
def __str__(self): |
||||
return repr('Set Cookie Error') |
||||
|
||||
|
||||
class GetCookieError(CookiePoolError): |
||||
def __str__(self): |
||||
return repr('Get Cookie Error') |
||||
|
||||
|
||||
class DeleteCookieError(CookiePoolError): |
||||
def __str__(self): |
||||
return repr('Delete Cookie Error') |
||||
|
||||
|
||||
class GetRandomCookieError(CookiePoolError): |
||||
def __str__(self): |
||||
return repr('Get Random Cookie Error') |
||||
|
||||
|
||||
class GetAllCookieError(CookiePoolError): |
||||
def __str__(self): |
||||
return repr('Get All Cookie Error') |
||||
|
||||
|
||||
class SetAccountError(CookiePoolError): |
||||
def __str__(self): |
||||
return repr('Set Account Error') |
||||
|
||||
|
||||
class DeleteAccountError(CookiePoolError): |
||||
def __str__(self): |
||||
return repr('Delete Account Error') |
||||
|
||||
|
||||
class GetAccountError(CookiePoolError): |
||||
def __str__(self): |
||||
return repr('Get Account Error') |
||||
|
||||
|
||||
class GetAllAccountError(CookiePoolError): |
||||
def __str__(self): |
||||
return repr('Get All Account Error') |
@ -1,139 +0,0 @@
|
||||
import time |
||||
|
||||
import requests |
||||
from requests.exceptions import ConnectionError |
||||
|
||||
from cookiespool.config import * |
||||
|
||||
|
||||
class Yundama(): |
||||
def __init__(self, username, password, app_id, app_key, api_url=YUNDAMA_API_URL): |
||||
self.username = username |
||||
self.password = password |
||||
self.app_id = str(app_id) if not isinstance(app_id, str) else app_id |
||||
self.app_key = app_key |
||||
self.api_url = api_url |
||||
|
||||
def login(self): |
||||
""" |
||||
登录云打码账户 |
||||
:return: |
||||
""" |
||||
try: |
||||
data = {'method': 'login', 'username': self.username, 'password': self.password, 'appid': self.app_id, |
||||
'appkey': self.app_key} |
||||
response = requests.post(self.api_url, data=data) |
||||
if response.status_code == 200: |
||||
result = response.json() |
||||
print(result) |
||||
if 'ret' in result.keys() and result.get('ret') < 0: |
||||
return self.error(result.get('ret')) |
||||
else: |
||||
return result |
||||
return None |
||||
except ConnectionError: |
||||
return None |
||||
|
||||
def upload(self, files, timeout, code_type): |
||||
""" |
||||
上传验证码得到识别结果 |
||||
:param files: |
||||
:param timeout: |
||||
:param code_type: |
||||
:return: |
||||
""" |
||||
try: |
||||
data = {'method': 'upload', 'username': self.username, 'password': self.password, 'appid': self.app_id, |
||||
'appkey': self.app_key, 'codetype': str(code_type), 'timeout': str(timeout)} |
||||
response = requests.post(self.api_url, data=data, files=files) |
||||
if response.status_code == 200: |
||||
return response.json() |
||||
return None |
||||
except ConnectionError: |
||||
return None |
||||
|
||||
def retry(self, cid, try_count=1): |
||||
""" |
||||
临时识别不出, 传入cid重试 |
||||
:param cid: 验证码ID |
||||
:param try_count: 重试次数 |
||||
:return: 验证码结果 |
||||
""" |
||||
if try_count >= YUNDAMA_MAX_RETRY: |
||||
return None |
||||
print('Retrying: ', cid, 'Count: ', try_count) |
||||
time.sleep(2) |
||||
try: |
||||
data = {'method': 'result', 'cid': cid} |
||||
print(data) |
||||
response = requests.post(self.api_url, data=data) |
||||
if response.status_code == 200: |
||||
result = response.json() |
||||
print(result) |
||||
if 'ret' in result.keys() and result.get('ret') < 0: |
||||
print(self.error(result.get('ret'))) |
||||
if result.get('ret') == 0 and 'text' in result.keys(): |
||||
return result.get('text') |
||||
else: |
||||
return self.retry(cid, try_count + 1) |
||||
return None |
||||
except ConnectionError: |
||||
return None |
||||
|
||||
def identify(self, file=None, stream=None, timeout=60, code_type=5000): |
||||
""" |
||||
主函数 |
||||
:param file: 文件名 |
||||
:param stream: 文件流, 优先于文件名 |
||||
:param timeout: 超时时间 |
||||
:param code_type: 验证码类型 |
||||
:return: 识别结果 |
||||
""" |
||||
if stream: |
||||
files = {'file': stream} |
||||
elif file: |
||||
files = {'file': open(file, 'rb')} |
||||
else: |
||||
return None |
||||
result = self.upload(files, timeout, code_type) |
||||
if 'ret' in result.keys() and result.get('ret') < 0: |
||||
print(self.error(result.get('ret'))) |
||||
if result.get('text'): |
||||
print('验证码识别成功', result.get('text')) |
||||
return result.get('text') |
||||
else: |
||||
return self.retry(result.get('cid')) |
||||
|
||||
def error(self, code): |
||||
""" |
||||
报错原因 |
||||
:param code: 错误码 |
||||
:return: 错误原因 |
||||
""" |
||||
map = { |
||||
-1001: '密码错误', |
||||
-1002: '软件ID/密钥有误', |
||||
-1003: '用户被封', |
||||
-1004: 'IP被封', |
||||
-1005: '软件被封', |
||||
-1006: '登录IP与绑定的区域不匹配', |
||||
-1007: '账号余额为零', |
||||
-2001: '验证码类型有误', |
||||
-2002: '验证码图片太大', |
||||
-2003: '验证码图片损坏', |
||||
-2004: '上传验证码图片失败', |
||||
-3001: '验证码ID不存在 ', |
||||
-3002: '验证码还在识别', |
||||
-3003: '验证码识别超时', |
||||
-3004: '验证码看不清', |
||||
-3005: '验证码报错失败', |
||||
-4001: '充值卡号不正确或已使用', |
||||
-5001: '注册用户失败' |
||||
} |
||||
return '云打码' + map.get(code) |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
ydm = Yundama(YUNDAMA_USERNAME, YUNDAMA_PASSWORD, YUNDAMA_APP_ID, YUNDAMA_APP_KEY) |
||||
result = ydm.identify(file='getimage.jpg') |
||||
print(result) |
@ -0,0 +1,6 @@
|
||||
from cookiespool.tester import WeiboValidTester |
||||
|
||||
if __name__ == '__main__': |
||||
tester = WeiboValidTester() |
||||
|
||||
tester.run() |
@ -0,0 +1,231 @@
|
||||
import os |
||||
import time |
||||
from io import BytesIO |
||||
from PIL import Image |
||||
from selenium import webdriver |
||||
from selenium.common.exceptions import TimeoutException |
||||
from selenium.webdriver import ActionChains |
||||
from selenium.webdriver.common.by import By |
||||
from selenium.webdriver.support.ui import WebDriverWait |
||||
from selenium.webdriver.support import expected_conditions as EC |
||||
from os import listdir |
||||
from os.path import abspath, dirname |
||||
|
||||
TEMPLATES_FOLDER = dirname(abspath(__file__)) + '/templates/' |
||||
|
||||
|
||||
class WeiboCookies(): |
||||
def __init__(self, username, password, browser): |
||||
self.url = 'https://passport.weibo.cn/signin/login?entry=mweibo&r=https://m.weibo.cn/' |
||||
self.browser = browser |
||||
self.wait = WebDriverWait(self.browser, 20) |
||||
self.username = username |
||||
self.password = password |
||||
|
||||
def open(self): |
||||
""" |
||||
打开网页输入用户名密码并点击 |
||||
:return: None |
||||
""" |
||||
self.browser.delete_all_cookies() |
||||
self.browser.get(self.url) |
||||
username = self.wait.until(EC.presence_of_element_located((By.ID, 'loginName'))) |
||||
password = self.wait.until(EC.presence_of_element_located((By.ID, 'loginPassword'))) |
||||
submit = self.wait.until(EC.element_to_be_clickable((By.ID, 'loginAction'))) |
||||
username.send_keys(self.username) |
||||
password.send_keys(self.password) |
||||
time.sleep(1) |
||||
submit.click() |
||||
|
||||
def password_error(self): |
||||
""" |
||||
判断是否密码错误 |
||||
:return: |
||||
""" |
||||
try: |
||||
return WebDriverWait(self.browser, 5).until( |
||||
EC.text_to_be_present_in_element((By.ID, 'errorMsg'), '用户名或密码错误')) |
||||
except TimeoutException: |
||||
return False |
||||
|
||||
def login_successfully(self): |
||||
""" |
||||
判断是否登录成功 |
||||
:return: |
||||
""" |
||||
try: |
||||
return bool( |
||||
WebDriverWait(self.browser, 5).until(EC.presence_of_element_located((By.CLASS_NAME, 'drop-title')))) |
||||
except TimeoutException: |
||||
return False |
||||
|
||||
def get_position(self): |
||||
""" |
||||
获取验证码位置 |
||||
:return: 验证码位置元组 |
||||
""" |
||||
try: |
||||
img = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'patt-shadow'))) |
||||
except TimeoutException: |
||||
print('未出现验证码') |
||||
self.open() |
||||
time.sleep(2) |
||||
location = img.location |
||||
size = img.size |
||||
top, bottom, left, right = location['y'], location['y'] + size['height'], location['x'], location['x'] + size[ |
||||
'width'] |
||||
return (top, bottom, left, right) |
||||
|
||||
def get_screenshot(self): |
||||
""" |
||||
获取网页截图 |
||||
:return: 截图对象 |
||||
""" |
||||
screenshot = self.browser.get_screenshot_as_png() |
||||
screenshot = Image.open(BytesIO(screenshot)) |
||||
return screenshot |
||||
|
||||
def get_image(self, name='captcha.png'): |
||||
""" |
||||
获取验证码图片 |
||||
:return: 图片对象 |
||||
""" |
||||
top, bottom, left, right = self.get_position() |
||||
print('验证码位置', top, bottom, left, right) |
||||
screenshot = self.get_screenshot() |
||||
captcha = screenshot.crop((left, top, right, bottom)) |
||||
return captcha |
||||
|
||||
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: |
||||
""" |
||||
# 获得四个按点 |
||||
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'] |
||||
|
||||
def get_cookies(self): |
||||
""" |
||||
获取Cookies |
||||
:return: |
||||
""" |
||||
return self.browser.get_cookies() |
||||
|
||||
def main(self): |
||||
""" |
||||
破解入口 |
||||
:return: |
||||
""" |
||||
self.open() |
||||
if self.password_error(): |
||||
return { |
||||
'status': 2, |
||||
'content': '用户名或密码错误' |
||||
} |
||||
# 如果不需要验证码直接登录成功 |
||||
if self.login_successfully(): |
||||
cookies = self.get_cookies() |
||||
return { |
||||
'status': 1, |
||||
'content': cookies |
||||
} |
||||
# 获取验证码图片 |
||||
image = self.get_image('captcha.png') |
||||
numbers = self.detect_image(image) |
||||
self.move(numbers) |
||||
if self.login_successfully(): |
||||
cookies = self.get_cookies() |
||||
return { |
||||
'status': 1, |
||||
'content': cookies |
||||
} |
||||
else: |
||||
return { |
||||
'status': 3, |
||||
'content': '登录失败' |
||||
} |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
result = WeiboCookies('14773427930', 'x6pybpakq1').main() |
||||
print(result) |
After Width: | Height: | Size: 8.6 KiB |
After Width: | Height: | Size: 8.2 KiB |
After Width: | Height: | Size: 8.9 KiB |
After Width: | Height: | Size: 8.2 KiB |
After Width: | Height: | Size: 9.3 KiB |
After Width: | Height: | Size: 8.5 KiB |
After Width: | Height: | Size: 8.3 KiB |
After Width: | Height: | Size: 8.5 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 9.1 KiB |
After Width: | Height: | Size: 8.7 KiB |
After Width: | Height: | Size: 9.7 KiB |
After Width: | Height: | Size: 8.3 KiB |
After Width: | Height: | Size: 8.9 KiB |
After Width: | Height: | Size: 9.1 KiB |
After Width: | Height: | Size: 9.3 KiB |
After Width: | Height: | Size: 8.6 KiB |
After Width: | Height: | Size: 8.2 KiB |
After Width: | Height: | Size: 9.2 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 8.3 KiB |
After Width: | Height: | Size: 8.8 KiB |
After Width: | Height: | Size: 8.2 KiB |
After Width: | Height: | Size: 8.5 KiB |