├── .gitignore ├── README.md ├── cookiespool ├── __init__.py ├── api.py ├── config.py ├── db.py ├── error.py ├── generator.py ├── importer.py ├── scheduler.py ├── tester.py └── verify.py ├── importer.py ├── requirements.txt ├── run.py └── tests └── login.py /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | *.pyc 3 | ghostdriver.log -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CookiesPool / Cookies池 2 | 3 | 可扩展的Cookies池,目前对接了新浪微博,可自行扩展其他站点 4 | 5 | 优化版(推荐):[https://github.com/Python3WebSpider/CookiesPool](https://github.com/Python3WebSpider/CookiesPool) 6 | 7 | ## 安装 8 | 9 | ``` 10 | pip3 install -r requirements.txt 11 | ``` 12 | 13 | ## 基础配置 14 | 15 | 修改cookiespool/config.py 16 | 17 | ### 数据库配置 18 | 19 | account:weibo:账号 20 | 21 | cookies:weibo:账号 22 | 23 | Value分别为密码和Cookies 24 | 25 | 账号自行某宝购买 26 | 27 | Redis连接信息到cookiespool/config文件修改 28 | 29 | ### 云打码平台配置 30 | 31 | 到yundama.com注册开发者和普通用户。 32 | 33 | 开发者申请应用ID和KEY,普通用户用于充值登录。 34 | 35 | 配置信息到cookiespool/config文件修改 36 | 37 | 38 | ### 进程开关 39 | 40 | 配置信息到cookiespool/config文件修改 41 | 42 | ## 运行 43 | 44 | ``` 45 | python3 run.py 46 | ``` 47 | 48 | ## 批量导入 49 | 50 | ``` 51 | python3 importer.py 52 | ``` 53 | 54 | ``` 55 | 请输入账号密码组, 输入exit退出读入 56 | 18459748505----astvar3647 57 | 14760253606----gmidy8470 58 | 14760253607----uoyuic8427 59 | 18459749258----rktfye8937 60 | 账号 18459748505 密码 astvar3647 61 | 录入成功 62 | 账号 14760253606 密码 gmidy8470 63 | 录入成功 64 | 账号 14760253607 密码 uoyuic8427 65 | 录入成功 66 | 账号 18459749258 密码 rktfye8937 67 | 录入成功 68 | exit 69 | ``` 70 | 71 | ## 运行效果 72 | 73 | 开启Generator、API,关闭Tester为例: 74 | 75 | ``` 76 | Generating Cookies 77 | * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) 78 | Getting 526 accounts from Redis 79 | Getting Cookies of weibo uva68312006hao@163.com 1nr0nkwes7 80 | Generating Cookies of uva68312006hao@163.com 81 | 出现验证码,开始识别验证码 82 | Retrying: 1400009488 Count: 1 83 | {'cid': 1400009488, 'method': 'result'} 84 | {'cid': 1400009488, 'ret': -3002, 'text': ''} 85 | 云打码验证码还在识别 86 | Retrying: 1400009488 Count: 2 87 | {'cid': 1400009488, 'method': 'result'} 88 | {'cid': 1400009488, 'ret': -3002, 'text': ''} 89 | 云打码验证码还在识别 90 | Retrying: 1400009488 Count: 3 91 | {'cid': 1400009488, 'method': 'result'} 92 | {'cid': 1400009488, 'ret': -3002, 'text': ''} 93 | 云打码验证码还在识别 94 | Retrying: 1400009488 Count: 4 95 | {'cid': 1400009488, 'method': 'result'} 96 | {'cid': 1400009488, 'ret': -3002, 'text': ''} 97 | 云打码验证码还在识别 98 | Retrying: 1400009488 Count: 5 99 | {'cid': 1400009488, 'method': 'result'} 100 | {'cid': 1400009488, 'ret': 0, 'text': '4uh4h'} 101 | 登录成功 102 | [{'expiry': 1525092087.375918, 'secure': False, 'value': '0cS3YKcgWrDRsV', 'httpOnly': False, 'name': 'SUHB', 'path': '/', 'domain': '.weibo.cn'}, {'expiry': 1525092087.374765, 'secure': False, 'value': '0033WrSXqPxfM725Ws9jqgMF55529P9D9W5H8du8O9J-.X.2Zd-6LbEK5JpX5o2p5NHD95QfS0ecSKe7e0n4Ws4Dqcj.i--fiK.7iKn4i--ci-z7i-zRi--fi-2fiK.ci--4i-2pi-i2', 'httpOnly': False, 'name': 'SUBP', 'path': '/', 'domain': '.weibo.cn'}, {'expiry': 1525092087.373734, 'secure': False, 'value': '_2A250AasnDeThGeNJ6FQU8y7PwzWIHXVXDTVvrDV6PUJbktBeLRfukW0wFLR2922NnApkDOZb_eNrRfNBZQ..', 'httpOnly': True, 'name': 'SUB', 'path': '/', 'domain': '.weibo.cn'}, {'expiry': 1496148087.677672, 'secure': False, 'value': 'b2d2d84c5aafcc02c0dac2faa4acc43f', 'httpOnly': True, 'name': '_T_WM', 'path': '/', 'domain': '.weibo.cn'}, {'secure': False, 'value': '1493556087', 'httpOnly': False, 'name': 'SSOLoginState', 'path': '/', 'domain': '.weibo.cn'}, {'expiry': 1808916087.372439, 'secure': False, 'value': 'AnPRL-vKWx_-0LWAO4Mk79GSyD6FxNqsFzhSNFOZI1MxKni8Ok7C3thzDOuIdEdh-0SvYb25zbF-znxYY_Z5hK4.', 'httpOnly': True, 'name': 'SCF', 'path': '/', 'domain': '.weibo.cn'}, {'expiry': 1496148083.263736, 'secure': False, 'value': '1496148083', 'httpOnly': False, 'name': 'ALF', 'path': '/', 'domain': '.weibo.cn'}] 103 | {'SUHB': '0cS3YKcgWrDRsV', 'SUB': '_2A250AasnDeThGeNJ6FQU8y7PwzWIHXVXDTVvrDV6PUJbktBeLRfukW0wFLR2922NnApkDOZb_eNrRfNBZQ..', 'SUBP': '0033WrSXqPxfM725Ws9jqgMF55529P9D9W5H8du8O9J-.X.2Zd-6LbEK5JpX5o2p5NHD95QfS0ecSKe7e0n4Ws4Dqcj.i--fiK.7iKn4i--ci-z7i-zRi--fi-2fiK.ci--4i-2pi-i2', 'SCF': 'AnPRL-vKWx_-0LWAO4Mk79GSyD6FxNqsFzhSNFOZI1MxKni8Ok7C3thzDOuIdEdh-0SvYb25zbF-znxYY_Z5hK4.', 'SSOLoginState': '1493556087', '_T_WM': 'b2d2d84c5aafcc02c0dac2faa4acc43f', 'ALF': '1496148083'} 104 | 成功获取到Cookies 105 | Saving Cookies to Redis uva68312006hao@163.com {"SUHB": "0cS3YKcgWrDRsV", "SUB": "_2A250AasnDeThGeNJ6FQU8y7PwzWIHXVXDTVvrDV6PUJbktBeLRfukW0wFLR2922NnApkDOZb_eNrRfNBZQ..", "SUBP": "0033WrSXqPxfM725Ws9jqgMF55529P9D9W5H8du8O9J-.X.2Zd-6LbEK5JpX5o2p5NHD95QfS0ecSKe7e0n4Ws4Dqcj.i--fiK.7iKn4i--ci-z7i-zRi--fi-2fiK.ci--4i-2pi-i2", "SCF": "AnPRL-vKWx_-0LWAO4Mk79GSyD6FxNqsFzhSNFOZI1MxKni8Ok7C3thzDOuIdEdh-0SvYb25zbF-znxYY_Z5hK4.", "SSOLoginState": "1493556087", "_T_WM": "b2d2d84c5aafcc02c0dac2faa4acc43f", "ALF": "1496148083"} 106 | ``` 107 | -------------------------------------------------------------------------------- /cookiespool/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Germey/CookiesPool/baf1989fcbe56634907ce9c923d74a022740ee87/cookiespool/__init__.py -------------------------------------------------------------------------------- /cookiespool/api.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, g 2 | 3 | from cookiespool.config import * 4 | from cookiespool.db import * 5 | 6 | __all__ = ['app'] 7 | 8 | app = Flask(__name__) 9 | 10 | 11 | @app.route('/') 12 | def index(): 13 | return '

Welcome to Cookie Pool System

' 14 | 15 | 16 | def get_conn(): 17 | """ 18 | 获取 19 | :return: 20 | """ 21 | for name in GENERATOR_MAP: 22 | print(name) 23 | if not hasattr(g, name): 24 | setattr(g, name + '_cookies', eval('CookiesRedisClient' + '(name="' + name + '")')) 25 | setattr(g, name + '_account', eval('AccountRedisClient' + '(name="' + name + '")')) 26 | return g 27 | 28 | 29 | @app.route('//random') 30 | def random(name): 31 | """ 32 | 获取随机的Cookie, 访问地址如 /weibo/random 33 | :return: 随机Cookie 34 | """ 35 | g = get_conn() 36 | cookies = getattr(g, name + '_cookies').random() 37 | return cookies 38 | 39 | @app.route('//add//') 40 | def add(name, username, password): 41 | """ 42 | 添加用户, 访问地址如 /weibo/add/user/password 43 | """ 44 | g = get_conn() 45 | result = getattr(g, name + '_account').set(username, password) 46 | return result 47 | 48 | 49 | @app.route('//count') 50 | def count(name): 51 | """ 52 | 获取Cookies总数 53 | """ 54 | g = get_conn() 55 | count = getattr(g, name + '_cookies').count() 56 | return str(int) if isinstance(count, int) else count 57 | 58 | 59 | if __name__ == '__main__': 60 | app.run(host='0.0.0.0') 61 | -------------------------------------------------------------------------------- /cookiespool/config.py: -------------------------------------------------------------------------------- 1 | # Redis数据库地址 2 | REDIS_HOST = '' 3 | 4 | # Redis端口 5 | REDIS_PORT = 6379 6 | 7 | # Redis密码,如无填None 8 | REDIS_PASSWORD = '' 9 | 10 | # 配置信息,无需修改 11 | REDIS_DOMAIN = '*' 12 | REDIS_NAME = '*' 13 | 14 | # 云打码相关配置到yundama.com申请注册 15 | YUNDAMA_USERNAME = '' 16 | YUNDAMA_PASSWORD = '' 17 | YUNDAMA_APP_ID = '3372' 18 | YUNDAMA_APP_KEY = '1b586a30bfda5c7fa71c881075ba49d0' 19 | 20 | YUNDAMA_API_URL = 'http://api.yundama.com/api.php' 21 | 22 | # 云打码最大尝试次数 23 | YUNDAMA_MAX_RETRY = 20 24 | 25 | # 产生器默认使用的浏览器 26 | DEFAULT_BROWSER = 'Chrome' 27 | 28 | # 产生器类,如扩展其他站点,请在此配置 29 | GENERATOR_MAP = { 30 | 'weibo': 'WeiboCookiesGenerator' 31 | } 32 | 33 | # 测试类,如扩展其他站点,请在此配置 34 | TESTER_MAP = { 35 | 'weibo': 'WeiboValidTester' 36 | } 37 | 38 | # 产生器和验证器循环周期 39 | CYCLE = 120 40 | 41 | # API地址和端口 42 | API_HOST = '127.0.0.1' 43 | API_PORT = 5000 44 | 45 | # 进程开关 46 | # 产生器,模拟登录添加Cookies 47 | GENERATOR_PROCESS = True 48 | # 验证器,循环检测数据库中Cookies是否可用,不可用删除 49 | VALID_PROCESS = False 50 | # API接口服务 51 | API_PROCESS = True 52 | -------------------------------------------------------------------------------- /cookiespool/db.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | import redis 4 | 5 | from cookiespool.config import * 6 | from cookiespool.error import * 7 | 8 | 9 | class RedisClient(object): 10 | def __init__(self, host=REDIS_HOST, port=REDIS_PORT, password=REDIS_PASSWORD): 11 | """ 12 | 初始化Redis连接 13 | :param host: 地址 14 | :param port: 端口 15 | :param password: 密码 16 | """ 17 | if password: 18 | self._db = redis.Redis(host=host, port=port, password=password) 19 | else: 20 | self._db = redis.Redis(host=host, port=port) 21 | self.domain = REDIS_DOMAIN 22 | self.name = REDIS_NAME 23 | 24 | def _key(self, key): 25 | """ 26 | 得到格式化的key 27 | :param key: 最后一个参数key 28 | :return: 29 | """ 30 | return "{domain}:{name}:{key}".format(domain=self.domain, name=self.name, key=key) 31 | 32 | def set(self, key, value): 33 | """ 34 | 设置键值对 35 | :param key: 36 | :param value: 37 | :return: 38 | """ 39 | raise NotImplementedError 40 | 41 | def get(self, key): 42 | """ 43 | 根据键名获取键值 44 | :param key: 45 | :return: 46 | """ 47 | raise NotImplementedError 48 | 49 | def delete(self, key): 50 | """ 51 | 根据键名删除键值对 52 | :param key: 53 | :return: 54 | """ 55 | raise NotImplementedError 56 | 57 | def keys(self): 58 | """ 59 | 得到所有的键名 60 | :return: 61 | """ 62 | return self._db.keys('{domain}:{name}:*'.format(domain=self.domain, name=self.name)) 63 | 64 | def flush(self): 65 | """ 66 | 清空数据库, 慎用 67 | :return: 68 | """ 69 | self._db.flushall() 70 | 71 | 72 | class CookiesRedisClient(RedisClient): 73 | def __init__(self, host=REDIS_HOST, port=REDIS_PORT, password=REDIS_PASSWORD, domain='cookies', name='default'): 74 | """ 75 | 管理Cookies的对象 76 | :param host: 地址 77 | :param port: 端口 78 | :param password: 密码 79 | :param domain: 域, 如cookies, account等 80 | :param name: 名称, 一般为站点名, 如 weibo, 默认 default 81 | """ 82 | RedisClient.__init__(self, host, port, password) 83 | self.domain = domain 84 | self.name = name 85 | 86 | def set(self, key, value): 87 | try: 88 | self._db.set(self._key(key), value) 89 | except: 90 | raise SetCookieError 91 | 92 | def get(self, key): 93 | try: 94 | return self._db.get(self._key(key)).decode('utf-8') 95 | except: 96 | return None 97 | 98 | def delete(self, key): 99 | try: 100 | print('Delete', key) 101 | return self._db.delete(self._key(key)) 102 | except: 103 | raise DeleteCookieError 104 | 105 | def random(self): 106 | """ 107 | 随机得到一Cookies 108 | :return: 109 | """ 110 | try: 111 | keys = self.keys() 112 | return self._db.get(random.choice(keys)) 113 | except: 114 | raise GetRandomCookieError 115 | 116 | def all(self): 117 | """ 118 | 获取所有账户, 以字典形式返回 119 | :return: 120 | """ 121 | try: 122 | for key in self._db.keys('{domain}:{name}:*'.format(domain=self.domain, name=self.name)): 123 | group = key.decode('utf-8').split(':') 124 | if len(group) == 3: 125 | username = group[2] 126 | yield { 127 | 'username': username, 128 | 'cookies': self.get(username) 129 | } 130 | except Exception as e: 131 | print(e.args) 132 | raise GetAllCookieError 133 | 134 | def count(self): 135 | """ 136 | 获取当前Cookies数目 137 | :return: 数目 138 | """ 139 | return len(self.keys()) 140 | 141 | 142 | 143 | class AccountRedisClient(RedisClient): 144 | def __init__(self, host=REDIS_HOST, port=REDIS_PORT, password=REDIS_PASSWORD, domain='account', name='default'): 145 | RedisClient.__init__(self, host, port, password) 146 | self.domain = domain 147 | self.name = name 148 | 149 | def set(self, key, value): 150 | try: 151 | return self._db.set(self._key(key), value) 152 | except: 153 | raise SetAccountError 154 | 155 | def get(self, key): 156 | try: 157 | return self._db.get(self._key(key)).decode('utf-8') 158 | except: 159 | raise GetAccountError 160 | 161 | def all(self): 162 | """ 163 | 获取所有账户, 以字典形式返回 164 | :return: 165 | """ 166 | try: 167 | for key in self._db.keys('{domain}:{name}:*'.format(domain=self.domain, name=self.name)): 168 | group = key.decode('utf-8').split(':') 169 | if len(group) == 3: 170 | username = group[2] 171 | yield { 172 | 'username': username, 173 | 'password': self.get(username) 174 | } 175 | except Exception as e: 176 | print(e.args) 177 | raise GetAllAccountError 178 | 179 | def delete(self, key): 180 | """ 181 | 通过用户名删除用户 182 | :param key: 183 | :return: 184 | """ 185 | try: 186 | return self._db.delete(self._key(key)) 187 | except: 188 | raise DeleteAccountError 189 | 190 | 191 | if __name__ == '__main__': 192 | """ 193 | conn = CookiesRedisClient() 194 | conn.set('name', 'Mike') 195 | conn.set('name2', 'Bob') 196 | conn.set('name3', 'Amy') 197 | print(conn.get('name')) 198 | conn.delete('name') 199 | print(conn.keys()) 200 | print(conn.random()) 201 | """ 202 | # 测试 203 | conn = AccountRedisClient(name='weibo') 204 | conn2 = AccountRedisClient(name='mweibo') 205 | 206 | 207 | accounts = conn.all() 208 | for account in accounts: 209 | conn2.set(account['username'], account['password']) 210 | -------------------------------------------------------------------------------- /cookiespool/error.py: -------------------------------------------------------------------------------- 1 | class CookiePoolError(Exception): 2 | def __str__(self): 3 | return repr('Cookie Pool Error') 4 | 5 | 6 | class SetCookieError(CookiePoolError): 7 | def __str__(self): 8 | return repr('Set Cookie Error') 9 | 10 | 11 | class GetCookieError(CookiePoolError): 12 | def __str__(self): 13 | return repr('Get Cookie Error') 14 | 15 | 16 | class DeleteCookieError(CookiePoolError): 17 | def __str__(self): 18 | return repr('Delete Cookie Error') 19 | 20 | 21 | class GetRandomCookieError(CookiePoolError): 22 | def __str__(self): 23 | return repr('Get Random Cookie Error') 24 | 25 | 26 | class GetAllCookieError(CookiePoolError): 27 | def __str__(self): 28 | return repr('Get All Cookie Error') 29 | 30 | 31 | class SetAccountError(CookiePoolError): 32 | def __str__(self): 33 | return repr('Set Account Error') 34 | 35 | 36 | class DeleteAccountError(CookiePoolError): 37 | def __str__(self): 38 | return repr('Delete Account Error') 39 | 40 | 41 | class GetAccountError(CookiePoolError): 42 | def __str__(self): 43 | return repr('Get Account Error') 44 | 45 | 46 | class GetAllAccountError(CookiePoolError): 47 | def __str__(self): 48 | return repr('Get All Account Error') 49 | -------------------------------------------------------------------------------- /cookiespool/generator.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import requests 4 | import time 5 | from selenium import webdriver 6 | from selenium.common.exceptions import WebDriverException, TimeoutException 7 | from selenium.webdriver import DesiredCapabilities 8 | from selenium.webdriver.common.by import By 9 | from selenium.webdriver.support import expected_conditions as EC 10 | from selenium.webdriver.support.ui import WebDriverWait 11 | 12 | from cookiespool.config import * 13 | from cookiespool.db import CookiesRedisClient, AccountRedisClient 14 | from cookiespool.verify import Yundama 15 | 16 | 17 | class CookiesGenerator(object): 18 | def __init__(self, name='default', browser_type=DEFAULT_BROWSER): 19 | """ 20 | 父类, 初始化一些对象 21 | :param name: 名称 22 | :param browser: 浏览器, 若不使用浏览器则可设置为 None 23 | """ 24 | self.name = name 25 | self.cookies_db = CookiesRedisClient(name=self.name) 26 | self.account_db = AccountRedisClient(name=self.name) 27 | self.browser_type = browser_type 28 | 29 | def _init_browser(self, browser_type): 30 | """ 31 | 通过browser参数初始化全局浏览器供模拟登录使用 32 | :param browser: 浏览器 PhantomJS/ Chrome 33 | :return: 34 | """ 35 | if browser_type == 'PhantomJS': 36 | caps = DesiredCapabilities.PHANTOMJS 37 | caps[ 38 | "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' 39 | self.browser = webdriver.PhantomJS(desired_capabilities=caps) 40 | self.browser.set_window_size(1400, 500) 41 | elif browser_type == 'Chrome': 42 | self.browser = webdriver.Chrome() 43 | 44 | def new_cookies(self, username, password): 45 | raise NotImplementedError 46 | 47 | def set_cookies(self, account): 48 | """ 49 | 根据账户设置新的Cookies 50 | :param account: 51 | :return: 52 | """ 53 | results = self.new_cookies(account.get('username'), account.get('password')) 54 | if results: 55 | username, cookies = results 56 | print('Saving Cookies to Redis', username, cookies) 57 | self.cookies_db.set(username, cookies) 58 | 59 | 60 | def run(self): 61 | """ 62 | 运行, 得到所有账户, 然后顺次模拟登录 63 | :return: 64 | """ 65 | accounts = self.account_db.all() 66 | cookies = self.cookies_db.all() 67 | # Account 中对应的用户 68 | accounts = list(accounts) 69 | # Cookies中对应的用户 70 | valid_users = [cookie.get('username') for cookie in cookies] 71 | print('Getting', len(accounts), 'accounts from Redis') 72 | if len(accounts): 73 | self._init_browser(browser_type=self.browser_type) 74 | for account in accounts: 75 | if not account.get('username') in valid_users: 76 | print('Getting Cookies of ', self.name, account.get('username'), account.get('password')) 77 | self.set_cookies(account) 78 | print('Generator Run Finished') 79 | 80 | def close(self): 81 | try: 82 | print('Closing Browser') 83 | self.browser.close() 84 | del self.browser 85 | except TypeError: 86 | print('Browser not opened') 87 | 88 | 89 | class WeiboCookiesGenerator(CookiesGenerator): 90 | def __init__(self, name='weibo', browser_type=DEFAULT_BROWSER): 91 | """ 92 | 初始化操作, 微博需要声明一个云打码引用 93 | :param name: 名称微博 94 | :param browser: 使用的浏览器 95 | """ 96 | CookiesGenerator.__init__(self, name, browser_type) 97 | self.name = name 98 | self.ydm = Yundama(YUNDAMA_USERNAME, YUNDAMA_PASSWORD, YUNDAMA_APP_ID, YUNDAMA_APP_KEY) 99 | 100 | def _success(self, username): 101 | wait = WebDriverWait(self.browser, 5) 102 | success = wait.until(EC.visibility_of_element_located((By.CLASS_NAME, 'me_portrait_w'))) 103 | if success: 104 | print('登录成功') 105 | self.browser.get('http://weibo.cn/') 106 | 107 | if "我的首页" in self.browser.title: 108 | print(self.browser.get_cookies()) 109 | cookies = {} 110 | for cookie in self.browser.get_cookies(): 111 | cookies[cookie["name"]] = cookie["value"] 112 | print(cookies) 113 | print('成功获取到Cookies') 114 | return (username, json.dumps(cookies)) 115 | 116 | def new_cookies(self, username, password): 117 | """ 118 | 生成Cookies 119 | :param username: 用户名 120 | :param password: 密码 121 | :return: 用户名和Cookies 122 | """ 123 | print('Generating Cookies of', username) 124 | self.browser.delete_all_cookies() 125 | self.browser.get('http://my.sina.com.cn/profile/unlogin') 126 | wait = WebDriverWait(self.browser, 20) 127 | 128 | try: 129 | login = wait.until(EC.visibility_of_element_located((By.ID, 'hd_login'))) 130 | login.click() 131 | user = wait.until( 132 | EC.visibility_of_element_located((By.CSS_SELECTOR, '.loginformlist input[name="loginname"]'))) 133 | user.send_keys(username) 134 | psd = wait.until( 135 | EC.visibility_of_element_located((By.CSS_SELECTOR, '.loginformlist input[name="password"]'))) 136 | psd.send_keys(password) 137 | submit = wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, '.login_btn'))) 138 | submit.click() 139 | try: 140 | result = self._success(username) 141 | if result: 142 | return result 143 | except TimeoutException: 144 | print('出现验证码,开始识别验证码') 145 | yzm = wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, '.loginform_yzm .yzm'))) 146 | url = yzm.get_attribute('src') 147 | cookies = self.browser.get_cookies() 148 | cookies_dict = {} 149 | for cookie in cookies: 150 | cookies_dict[cookie.get('name')] = cookie.get('value') 151 | response = requests.get(url, cookies=cookies_dict) 152 | result = self.ydm.identify(stream=response.content) 153 | if not result: 154 | print('验证码识别失败, 跳过识别') 155 | return 156 | door = wait.until( 157 | EC.visibility_of_element_located((By.CSS_SELECTOR, '.loginform_yzm input[name="door"]'))) 158 | door.send_keys(result) 159 | submit.click() 160 | result = self._success(username) 161 | if result: 162 | return result 163 | except WebDriverException as e: 164 | print(e.args) 165 | 166 | 167 | class MWeiboCookiesGenerator(CookiesGenerator): 168 | def __init__(self, name='weibo', browser_type=DEFAULT_BROWSER): 169 | """ 170 | 初始化操作, 微博需要声明一个云打码引用 171 | :param name: 名称微博 172 | :param browser: 使用的浏览器 173 | """ 174 | CookiesGenerator.__init__(self, name, browser_type) 175 | self.name = name 176 | self.ydm = Yundama(YUNDAMA_USERNAME, YUNDAMA_PASSWORD, YUNDAMA_APP_ID, YUNDAMA_APP_KEY) 177 | 178 | def _success(self, username): 179 | wait = WebDriverWait(self.browser, 5) 180 | success = wait.until(EC.visibility_of_element_located((By.CLASS_NAME, 'me_portrait_w'))) 181 | 182 | if success: 183 | print('登录成功') 184 | self.browser.get('http://m.weibo.cn/') 185 | 186 | if "微博" in self.browser.title: 187 | print(self.browser.get_cookies()) 188 | cookies = {} 189 | for cookie in self.browser.get_cookies(): 190 | cookies[cookie["name"]] = cookie["value"] 191 | print(cookies) 192 | print('成功获取到Cookies') 193 | return (username, json.dumps(cookies)) 194 | 195 | def new_cookies(self, username, password): 196 | """ 197 | 生成Cookies 198 | :param username: 用户名 199 | :param password: 密码 200 | :return: 用户名和Cookies 201 | """ 202 | print('Generating Cookies of', username) 203 | self.browser.delete_all_cookies() 204 | self.browser.get('http://my.sina.com.cn/profile/unlogin') 205 | wait = WebDriverWait(self.browser, 20) 206 | 207 | try: 208 | login = wait.until(EC.visibility_of_element_located((By.ID, 'hd_login'))) 209 | login.click() 210 | 211 | user = wait.until( 212 | EC.visibility_of_element_located((By.CSS_SELECTOR, '.loginformlist input[name="loginname"]'))) 213 | user.send_keys(username) 214 | psd = wait.until( 215 | EC.visibility_of_element_located((By.CSS_SELECTOR, '.loginformlist input[name="password"]'))) 216 | psd.send_keys(password) 217 | submit = wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, '.login_btn'))) 218 | submit.click() 219 | try: 220 | result = self._success(username) 221 | if result: 222 | return result 223 | except TimeoutException: 224 | print('出现验证码,开始识别验证码') 225 | yzm = wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, '.loginform_yzm .yzm'))) 226 | url = yzm.get_attribute('src') 227 | cookies = self.browser.get_cookies() 228 | 229 | cookies_dict = {} 230 | for cookie in cookies: 231 | cookies_dict[cookie.get('name')] = cookie.get('value') 232 | response = requests.get(url, cookies=cookies_dict) 233 | result = self.ydm.identify(stream=response.content) 234 | if not result: 235 | print('验证码识别失败, 跳过识别') 236 | return 237 | door = wait.until( 238 | EC.visibility_of_element_located((By.CSS_SELECTOR, '.loginform_yzm input[name="door"]'))) 239 | door.send_keys(result) 240 | submit.click() 241 | result = self._success(username) 242 | if result: 243 | return result 244 | except WebDriverException as e: 245 | pass 246 | 247 | 248 | if __name__ == '__main__': 249 | generator = WeiboCookiesGenerator() 250 | generator._init_browser('Chrome') 251 | generator.new_cookies('15197170054', 'gmwkms222') 252 | -------------------------------------------------------------------------------- /cookiespool/importer.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | from cookiespool.db import AccountRedisClient 4 | 5 | conn = AccountRedisClient(name='weibo') 6 | 7 | def set(account, sep='----'): 8 | username, password = account.split(sep) 9 | result = conn.set(username, password) 10 | print('账号', username, '密码', password) 11 | print('录入成功' if result else '录入失败') 12 | 13 | 14 | def scan(): 15 | print('请输入账号密码组, 输入exit退出读入') 16 | while True: 17 | account = input() 18 | if account == 'exit': 19 | break 20 | set(account) 21 | 22 | 23 | if __name__ == '__main__': 24 | scan() -------------------------------------------------------------------------------- /cookiespool/scheduler.py: -------------------------------------------------------------------------------- 1 | import time 2 | from multiprocessing import Process 3 | 4 | from cookiespool.api import app 5 | from cookiespool.config import * 6 | from cookiespool.generator import * 7 | from cookiespool.tester import * 8 | 9 | class Scheduler(object): 10 | @staticmethod 11 | def valid_cookie(cycle=CYCLE): 12 | while True: 13 | print('Checking Cookies') 14 | try: 15 | for name, cls in TESTER_MAP.items(): 16 | tester = eval(cls + '(name="' + name + '")') 17 | tester.run() 18 | print('Tester Finished') 19 | del tester 20 | time.sleep(cycle) 21 | except Exception as e: 22 | print(e.args) 23 | 24 | @staticmethod 25 | def generate_cookie(cycle=CYCLE): 26 | while True: 27 | print('Generating Cookies') 28 | try: 29 | for name, cls in GENERATOR_MAP.items(): 30 | generator = eval(cls + '(name="' + name + '")') 31 | generator.run() 32 | print('Generator Finished') 33 | generator.close() 34 | print('Deleted Generator') 35 | time.sleep(cycle) 36 | except Exception as e: 37 | print(e.args) 38 | 39 | @staticmethod 40 | def api(): 41 | app.run(host=API_HOST, port=API_PORT) 42 | 43 | def run(self): 44 | if GENERATOR_PROCESS: 45 | generate_process = Process(target=Scheduler.generate_cookie) 46 | generate_process.start() 47 | 48 | if VALID_PROCESS: 49 | valid_process = Process(target=Scheduler.valid_cookie) 50 | valid_process.start() 51 | 52 | if API_PROCESS: 53 | api_process = Process(target=Scheduler.api) 54 | api_process.start() 55 | 56 | -------------------------------------------------------------------------------- /cookiespool/tester.py: -------------------------------------------------------------------------------- 1 | import json 2 | from bs4 import BeautifulSoup 3 | import requests 4 | from requests.exceptions import ConnectionError 5 | from cookiespool.db import * 6 | from cookiespool.generator import WeiboCookiesGenerator 7 | 8 | 9 | class ValidTester(object): 10 | def __init__(self, name='default'): 11 | self.name = name 12 | self.cookies_db = CookiesRedisClient(name=self.name) 13 | self.account_db = AccountRedisClient(name=self.name) 14 | 15 | def test(self, account, cookies): 16 | raise NotImplementedError 17 | 18 | def run(self): 19 | accounts = self.cookies_db.all() 20 | for account in accounts: 21 | username = account.get('username') 22 | cookies = self.cookies_db.get(username) 23 | self.test(account, cookies) 24 | 25 | 26 | class WeiboValidTester(ValidTester): 27 | def __init__(self, name='weibo'): 28 | ValidTester.__init__(self, name) 29 | 30 | def test(self, account, cookies): 31 | print('Testing Account', account.get('username')) 32 | try: 33 | cookies = json.loads(cookies) 34 | except TypeError: 35 | # Cookie 格式不正确 36 | print('Invalid Cookies Value', account.get('username')) 37 | self.cookies_db.delete(account.get('username')) 38 | print('Deleted User', account.get('username')) 39 | return None 40 | try: 41 | response = requests.get('http://weibo.cn', cookies=cookies) 42 | if response.status_code == 200: 43 | html = response.text 44 | soup = BeautifulSoup(html, 'lxml') 45 | title = soup.title.string 46 | if title == '我的首页': 47 | print('Valid Cookies', account.get('username')) 48 | else: 49 | print('Title is', title) 50 | # Cookie已失效 51 | print('Invalid Cookies', account.get('username')) 52 | self.cookies_db.delete(account.get('username')) 53 | print('Deleted User', account.get('username')) 54 | except ConnectionError as e: 55 | print('Error', e.args) 56 | print('Invalid Cookies', account.get('username')) 57 | 58 | 59 | class MWeiboValidTester(ValidTester): 60 | def __init__(self, name='weibo'): 61 | ValidTester.__init__(self, name) 62 | 63 | def test(self, account, cookies): 64 | print('Testing Account', account.get('username')) 65 | try: 66 | cookies = json.loads(cookies) 67 | except TypeError: 68 | # Cookie 格式不正确 69 | print('Invalid Cookies Value', account.get('username')) 70 | self.cookies_db.delete(account.get('username')) 71 | print('Deleted User', account.get('username')) 72 | return None 73 | try: 74 | test_url = 'http://m.weibo.cn/api/container/getIndex?uid=1804544030&type=uid&page=1&containerid=1076031804544030' 75 | response = requests.get(test_url, cookies=cookies, timeout=5, allow_redirects=False) 76 | if response.status_code == 200: 77 | print('Valid Cookies', account.get('username')) 78 | else: 79 | print(response.status_code, response.headers) 80 | print('Invalid Cookies', account.get('username')) 81 | self.cookies_db.delete(account.get('username')) 82 | print('Deleted User', account.get('username')) 83 | except ConnectionError as e: 84 | print('Error', e.args) 85 | print('Invalid Cookies', account.get('username')) 86 | 87 | if __name__ == '__main__': 88 | tester = WeiboValidTester() 89 | tester.run() 90 | -------------------------------------------------------------------------------- /cookiespool/verify.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import requests 4 | from requests.exceptions import ConnectionError 5 | 6 | from cookiespool.config import * 7 | 8 | 9 | class Yundama(): 10 | def __init__(self, username, password, app_id, app_key, api_url=YUNDAMA_API_URL): 11 | self.username = username 12 | self.password = password 13 | self.app_id = str(app_id) if not isinstance(app_id, str) else app_id 14 | self.app_key = app_key 15 | self.api_url = api_url 16 | 17 | def login(self): 18 | """ 19 | 登录云打码账户 20 | :return: 21 | """ 22 | try: 23 | data = {'method': 'login', 'username': self.username, 'password': self.password, 'appid': self.app_id, 24 | 'appkey': self.app_key} 25 | response = requests.post(self.api_url, data=data) 26 | if response.status_code == 200: 27 | result = response.json() 28 | print(result) 29 | if 'ret' in result.keys() and result.get('ret') < 0: 30 | return self.error(result.get('ret')) 31 | else: 32 | return result 33 | return None 34 | except ConnectionError: 35 | return None 36 | 37 | def upload(self, files, timeout, code_type): 38 | """ 39 | 上传验证码得到识别结果 40 | :param files: 41 | :param timeout: 42 | :param code_type: 43 | :return: 44 | """ 45 | try: 46 | data = {'method': 'upload', 'username': self.username, 'password': self.password, 'appid': self.app_id, 47 | 'appkey': self.app_key, 'codetype': str(code_type), 'timeout': str(timeout)} 48 | response = requests.post(self.api_url, data=data, files=files) 49 | if response.status_code == 200: 50 | return response.json() 51 | return None 52 | except ConnectionError: 53 | return None 54 | 55 | def retry(self, cid, try_count=1): 56 | """ 57 | 临时识别不出, 传入cid重试 58 | :param cid: 验证码ID 59 | :param try_count: 重试次数 60 | :return: 验证码结果 61 | """ 62 | if try_count >= YUNDAMA_MAX_RETRY: 63 | return None 64 | print('Retrying: ', cid, 'Count: ', try_count) 65 | time.sleep(2) 66 | try: 67 | data = {'method': 'result', 'cid': cid} 68 | print(data) 69 | response = requests.post(self.api_url, data=data) 70 | if response.status_code == 200: 71 | result = response.json() 72 | print(result) 73 | if 'ret' in result.keys() and result.get('ret') < 0: 74 | print(self.error(result.get('ret'))) 75 | if result.get('ret') == 0 and 'text' in result.keys(): 76 | return result.get('text') 77 | else: 78 | return self.retry(cid, try_count + 1) 79 | return None 80 | except ConnectionError: 81 | return None 82 | 83 | def identify(self, file=None, stream=None, timeout=60, code_type=5000): 84 | """ 85 | 主函数 86 | :param file: 文件名 87 | :param stream: 文件流, 优先于文件名 88 | :param timeout: 超时时间 89 | :param code_type: 验证码类型 90 | :return: 识别结果 91 | """ 92 | if stream: 93 | files = {'file': stream} 94 | elif file: 95 | files = {'file': open(file, 'rb')} 96 | else: 97 | return None 98 | result = self.upload(files, timeout, code_type) 99 | if 'ret' in result.keys() and result.get('ret') < 0: 100 | print(self.error(result.get('ret'))) 101 | if result.get('text'): 102 | print('验证码识别成功', result.get('text')) 103 | return result.get('text') 104 | else: 105 | return self.retry(result.get('cid')) 106 | 107 | def error(self, code): 108 | """ 109 | 报错原因 110 | :param code: 错误码 111 | :return: 错误原因 112 | """ 113 | map = { 114 | -1001: '密码错误', 115 | -1002: '软件ID/密钥有误', 116 | -1003: '用户被封', 117 | -1004: 'IP被封', 118 | -1005: '软件被封', 119 | -1006: '登录IP与绑定的区域不匹配', 120 | -1007: '账号余额为零', 121 | -2001: '验证码类型有误', 122 | -2002: '验证码图片太大', 123 | -2003: '验证码图片损坏', 124 | -2004: '上传验证码图片失败', 125 | -3001: '验证码ID不存在 ', 126 | -3002: '验证码还在识别', 127 | -3003: '验证码识别超时', 128 | -3004: '验证码看不清', 129 | -3005: '验证码报错失败', 130 | -4001: '充值卡号不正确或已使用', 131 | -5001: '注册用户失败' 132 | } 133 | return '云打码' + map.get(code) 134 | 135 | 136 | if __name__ == '__main__': 137 | ydm = Yundama(YUNDAMA_USERNAME, YUNDAMA_PASSWORD, YUNDAMA_APP_ID, YUNDAMA_APP_KEY) 138 | result = ydm.identify(file='getimage.jpg') 139 | print(result) 140 | -------------------------------------------------------------------------------- /importer.py: -------------------------------------------------------------------------------- 1 | from cookiespool.importer import scan 2 | 3 | if __name__ == '__main__': 4 | scan() -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests>=2.13.0 2 | selenium>=3.4.0 3 | redis>=2.10.5 4 | Flask>=0.12.1 -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | from cookiespool.scheduler import Scheduler 2 | 3 | def main(): 4 | s = Scheduler() 5 | s.run() 6 | 7 | if __name__ == '__main__': 8 | main() -------------------------------------------------------------------------------- /tests/login.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from bs4 import BeautifulSoup 3 | 4 | cookies = { 5 | 'SSOLoginState': '1493555070', 6 | 'SCF': 'ArrMd41qtHmW87eTIsI-sT1IjDG8oncB9A0HbSmyDw1FwO5sbI_j6_ZellQQ07ZjTXTIBrM3Y_tpKym39f1tYWs.', 7 | 'SUB': '_2A250AacuDeRhGeRG6FIX9ybIzDiIHXVXDclmrDV6PUJbktBeLVfCkW18FWjD8r3ddYXy2abmqSauclujaw..', 8 | '_T_WM': '5e9c698a350ddeba1c5d77e1958af21b', 'ALF': '1496147067', 'SUHB': '0G0vWR88D2VokZ', 9 | 'SUBP': '0033WrSXqPxfM725Ws9jqgMF55529P9D9W5APD86CuQDllBusA-6OZaq5JpX5o2p5NHD95QE1he7SoMRShMXWs4Dqcjci--fi-zXiK.Xi--fi-iWiKnci--ciKn4iKy2i--Xi-zRi-2Ri--4iKL2iK.4i--Ri-2NiKnf' 10 | } 11 | 12 | response = requests.get('http://weibo.cn', cookies=cookies) 13 | if response.status_code == 200: 14 | html = response.text 15 | soup = BeautifulSoup(html, 'lxml') 16 | title = soup.title.string 17 | print(title) 18 | --------------------------------------------------------------------------------