美文网首页
Python爬虫 | cookie池

Python爬虫 | cookie池

作者: 生信师姐 | 来源:发表于2020-05-24 07:07 被阅读0次

    问题1:为什么要登陆
      很多时候,在没有登录的情况下,我们可以访问一部分页面或请求一些接口,因为毕竟网站本身需要做SEO,不会对所有页面都设置登录限制。但是,不登录直接爬取会有一些弊端,弊端主要有以下两点。

    1. 设置了登录限制的页面无法爬取。如某论坛设置了登录才可查看资源,某博客设置了登录才可查看全文等,这些页面都需要登录账号才可以查看和爬取。

    2.一些页面和接口虽然可以直接请求,但是请求一旦频繁,访问就容易被限制或者IP直接被封,但是登录之后就不会出现这样的问题,因此登录之后被反爬的可能性更低。登录账号可以降低被封禁的概率。

    问题2:为什么要搭建cookies池
      我们可以尝试登录之后再做爬取,被封禁的几率会小很多,但是也不能完全排除被封禁的风险。如果一直用同一个账号频繁请求,那就有可能遇到请求过于频繁而封号的问题。如果需要做大规模抓取,我们就需要拥有很多账号,每次请求随机选取一个账号,这样就降低了单个账号的访问频率,被封的概率又会大大降低。

    与IP代理池不同,Cookies池具有针对性,如果你爬微博就要构建一个微博cookies池,爬知乎就需要构建一个知乎cookies池;而IP代理池是通用的,可供不同的爬虫任务共同使用。

    比如,当构建微博cookies池时,我们需要一些微博账号,然后使用selenium模拟登录微博,识别验证码,登录成功后,获取该账号对应的cookies,存入redis数据库,从而维护一个微博cookies池,一个账号对应一个cookies。当构建好cookies池后,我们就可以直接通过cookies进行登录,而不需要再模拟登录,包括输入账号密码,识别验证码等操作。

    本篇博客中我们将使用redis,flask搭建一个可扩展的cookies池,这里我们将分别搭建微博和知乎的cookies池,之后任意站点的cookies池都可以通过类似的操作进行搭建,可以不断扩展,维护多个cookies池。

    一、本节目标

    我们以新浪微博为例来实现一个Cookies池的搭建过程。Cookies池中保存了许多新浪微博账号和登录后的Cookies信息,并且Cookies池还需要定时检测每个Cookies的有效性,如果某Cookies无效,那就删除该Cookies并模拟登录生成新的Cookies。同时Cookies池还需要一个非常重要的接口,即获取随机Cookies的接口,Cookies运行后,我们只需请求该接口,即可随机获得一个Cookies并用其爬取。

    由此可见,Cookies池需要有自动生成Cookies、定时检测Cookies、提供随机Cookies等几大核心功能。

    二、准备工作

    搭建之前肯定需要一些微博的账号。需要安装好Redis数据库并使其正常运行。需要安装Python的RedisPy、requests、Selelnium、Flask库。另外,还需要安装Chrome浏览器并配置好ChromeDriver。

    三、Cookies池架构

    4个核心模块:

    Cookies池架构的基本模块分为4块:存储模块、生成模块、检测模块、接口模块。每个模块的功能如下。

    • 存储模块负责存储每个账号的用户名密码以及每个账号对应的Cookies信息,同时还需要提供一些方法来实现方便的存取操作。

    • 生成模块负责生成新的Cookies。此模块会从存储模块逐个拿取账号的用户名和密码,然后模拟登录目标页面,判断登录成功,就将Cookies返回并交给存储模块存储。

    • 检测模块需要定时检测数据库中的Cookies。在这里我们需要设置一个检测链接,不同的站点检测链接不同,检测模块会逐个拿取账号对应的Cookies去请求链接,如果返回的状态是有效的,那么此Cookies没有失效,否则Cookies失效并移除。接下来等待生成模块重新生成即可。

    • 接口模块需要用API来提供对外服务的接口。由于可用的Cookies可能有多个,我们可以随机返回Cookies的接口,这样保证每个Cookies都有可能被取到。Cookies越多,每个Cookies被取到的概率就会越小,从而减少被封号的风险。

    还有配置文件config,py和调度模块scheduler.py

    以上设计Cookies池的的基本思路和前面讲的代理池有相似之处。接下来我们设计整体的架构,然后用代码实现该Cookies池。

    四、Cookies池的实现

    首先分别了解各个模块的实现过程。

    配置模块

    存储一些各个模块中需要使用的变量和参数配置config.py

    # Redis数据库地址、端口、密码
    REDIS_HOST =  "localhost"               # 注意本地就写字符串形式的"localhost"
    REDIS_PORT = 6379
    REDIS_PASSWORD =  None                  # 密码没有的话,就写None
    
    # 产生器使用的浏览器
    BROSER_TYPE = "chrome"                  # 注意浏览器类型,是字符串形式的
    
    # 产生器类,如扩展其他站点,请在此配置
    GENERATOR_MAP = {                       # 注意字典后的类名也是字符串形式的
        "weibo":"WeiboCookiesGenerator",
        "zhihu":"ZhihuCookiesGenerator"
    }
    
    # 测试类,如扩展其他站点,请在此配置
    TESTER_MAP ={
        "weibo": "WeiboValidTester",
        "zhihu": "WeiboValidTester"
        # 'XXX':'XXXCookiesGenerator'
    }
    
    TESTER_URL_MAP = {
        'weibo': 'https://www.weibo.com',
        'zhihu': 'https://www.zhihu.com'
    }
    
    # 产生器和验证器循环周期
    CYCLE = 120
    
    # API地址和端口
    API_HOST = '127.0.0.1'                  # host是字符串
    API_PORT = 5000                         # port不是字符串
    
    # 产生器开关,模拟登录添加Cookies
    GENERATOR_PROCESS = True
    
    # 验证器开关,循环检测数据库中Cookies是否可用,不可用删除
    TESTER_PROCESS = True
    
    # API接口服务
    API_PROCESS = True
    

    1. 存储模块

    需要存储的内容包括账号信息与Cookies信息。

    • 账号由用户名和密码两部分组成,所以可以存成用户名和密码的映射。
    • Cookies可以存成JSON字符串,但是后面得需要根据账号来生成Cookies,需要知道哪些账号已经生成了Cookies,哪些没有生成,所以需要同时保存该Cookies对应的用户名信息,其实也是用户名和Cookies的映射

    这里就是两组映射,Redis的Hash有天然的优势,所以这里我们就建立两个Hash。Hash的Key就是账号,Value对应着密码或者Cookies。

    注意 : 由于Cookies池需要做到可扩展,存储的账号和Cookies不一定单单只有本例中的微博,其他站点同样可以对接此Cookies池,所以这里Hash的名称可以做二级分类。例如,存账号的Hash名称可以为accounts:weibo,存Cookies的Hash名称可以为cookies:weibo。如要扩展知乎的Cookies池,我们就可以使用accounts:zhihu和cookies:zhihu,这样比较方便。

    接下来我们创建一个存储模块类,用以提供一些Hash的基本操作。有两个数据库,一个负责存储每个帐号的用户名和密码,另一个存储用户名和Cookies 信息。

    db.py

    import random
    import redis
    from CookiesPool.cookiespool.config import *  # 使用*的形式,就可以全部引入,不加config了
    
    class ReidsClient:
        def __ini__(self,type,website,host=REDIS_HOST,port=REDIS_PORT,password=REDIS_PASSWORD):
            '''
            初始化Redis链接
            '''
            self.db = redis.StrictRedis(host=host,port=port,password=password,decode_responses=True)
            self.type = type
            self.website = website
    
        @property                                           # 设置成类属性,这样,后面使用就不用加()
        def name(self):
            '''
            获取表的名字
            '''
            return"{}:{}".format(self.type,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):                                   # 注意,一定不要用del,会和内置的del冲突
            """
            根据键名删除键值对
            :param username: 用户名
            :return: 删除结果
            """
            return self.db.hdel(self.name,username)
    
        def username(self):
            """
            获取所有账户信息                                        # 在generator模块,会比较accounts和cookies用户名进而,accounts中有而cookies中没有的用户名,会用来模拟登陆,生成cookies
            :return: 所有用户名
            """
            return self.db.hkeys(self.name)
    
        def all(self):
            """
            获取所有键值对
            :return: 用户名和密码或Cookies的映射表
            """
            return self.db.hgetall(self.name)
    
        def count(self):
            """
            获取数目
            :return: 数目
            """
            return self.db.hlen(self.name)                          # 注意,使用的是hlen,没有hcount方法
    
        def random(self):
            """
            随机得到键值,用于随机Cookies获取
            :return: 随机Cookies
            """
            return random.choice(self.db.hvals(self.name))          # 注意是random.choice,不是random.random这是取0-1内的小数。
    
    
    
    if __name__ == '__main__':
        conn = ReidsClient("acounts","weibo")
        result = conn.set('kvradspgbnqonhv-eow693@yahoo.com', 'Xdnxwovqp9')
        print(result)
    

    (1)新建了一个RedisClient类,因为所有的账号和cookies是存储在redis中的,所以类名是RedisClient,之后对哈希表的操作,都是针对redis数据库的操作,所以要明白存储模块的类名为什么叫RedisClient;

    (2)初始化init()方法里,首先初始化StrictRedis对象,建立Redis连接。然后有两个关键参数type和website,分别代表类型和站点名称,它们就是用来拼接Hash名称的两个字段。

    (3)name()方法拼接了type和website,组成Hash的名称;如果这是存储账户的Hash,那么此处的type为accounts、website为weibo,如果是存储Cookies的Hash,那么此处的type为cookies、website为weibo。加@property设置成类属性,这样,后面方法使用就不用加()当做方法来使用了。

    (4)set()、get()、delete()方法分别代表设置、获取、删除Hash的某一个键值对;

    (5)username()方法,获取accounts或者cookies里的用户名信息。在generator模块,会比较accounts和cookies用户名accounts中有而cookies中没有的用户名,会用来模拟登陆,生成cookies。

    (6)all()方法是取出cookies表里面的所有键值对,在tester的时候会将取出来的进行验证,是否有效;

    (7)count()获取Hash的长度;

    (8)random()方法比较重要,它主要用于从Hash里随机选取一个Cookies并返回。每调用一次random()方法,就会获得随机的Cookies,此方法与接口模块对接即可实现请求接口获取随机Cookies。取的hvals,所以不会取到用户名,在API模块随机获取cookies会用到。

    2. 生成模块

    生成模块负责获取各个账号信息并模拟登录,随后生成Cookies并保存。我们首先获取两个Hash的信息,看看账户的Hash比Cookies的Hash多了哪些还没有生成Cookies的账号,然后将剩余的账号遍历,再去生成Cookies即可。

    generator.py

    import json
    from selenium import webdriver
    from selenium.webdriver import DesiredCapabilities
    from CookiesPool.cookiespool.config import *
    from CookiesPool.cookiespool.db import RedisClient
    from CookiesPool.weibologin.loginweibo import LoginWeibo
    from CookiesPool.zhihulogin.loginzhihu import Zhihu
    
    
    class CookiesGenerator(object):
        def __init__(self, website='default'):
            """
            父类, 初始化一些对象
            :param website: 名称
            :param browser: 浏览器, 若不使用浏览器则可设置为 None
            """
            self.website = website
            self.cookies_db = RedisClient('cookies', self.website)  # 存储用户名和cookies
            self.accounts_db = RedisClient('accounts', self.website)  # 存储用户名和密码
            self.init_browser()
    
        def __del__(self):
            self.close()
    
        def init_browser(self):
            """
            通过browser参数初始化全局浏览器供模拟登录使用
            :return:
            """
            # self.option = webdriver.ChromeOptions()
            # self.option.add_argument('headless')
            # self.browser = webdriver.Chrome(executable_path='F:\BaiduNetdiskDownload\cookie_pool\chromedriver',chrome_options=self.option)
    
            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 self.website == 'weibo':
                        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'))
                            
                    if self.website == 'zhihu':
                        if result.get('status') == 1:
                            cookies = result.get('content')
                            print('成功获取到Cookies', cookies)
                            if self.cookies_db.set(username, json.dumps(cookies)):
                                print('成功保存Cookies')
            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 LoginWeibo(username, password, self.browser).login()
    
    
    class ZhihuCookiesGenerator(CookiesGenerator):
        def __init__(self, website='zhihu'):
            """
            初始化操作
            :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 Zhihu(username, password).login()
    
    
    if __name__ == '__main__':
        generator = WeiboCookiesGenerator()
        generator.run()
    

    逻辑:
    (1)定义一个Generator类,初始化方法init()里面要实例化accounts和cookies的db,除此之外还有一个主脚本和new_cookies()

    (2)主程序run()方法

    • 先调用2个db的username()方法,将它们各自的用户名取出来。然后通过if判断,将accounts中有,而cookies中没有的username查出来,然后调用new_cookies()生成cookies,因为每个网站对应的有自己的登录方式,所以在此将它们写到各自的子类中分别实现。

    • 模拟登陆完之后,会传递回来一个字典result,根据result中的status键的状态码来判断是否登陆成功。在生成模块里我们可以根据不同的状态码做不同的处理。

    • result.get('status') ==1成功获取cookies,然后就result.get('content')取出cookies进行加工,具体实现是将cookies中的name-value键值对重新赋值给字典,然后进行json.dum序列化。然后传递给db的set方法进行存储cookies;

    • result.get('status') == 2就是失败了,账号h或密码错误,从accounts中将此账号的信息删除self.accounts_db.delete(username)

    • 如状态码为3的情况,则代表登录失败的一些错误,此时不能判断是否用户名或密码错误,也不能成功获取Cookies,那么简单提示再进行下一个处理即可。

    result ={
        'status': 1,
        'content': cookies
    }
    
    if self.website == 'weibo':
        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'))
    

    注意:字典中取某个键的值的方式 result.get('content')

    (3)如果要扩展其他站点,只需要实现new_cookies()方法即可。

    代码运行之后就会遍历一次尚未生成Cookies的账号,模拟登录生成新的Cookies

    chaojiying.py

    import requests
    from hashlib import md5
     
     
    class Chaojiying(object):
     
        def __init__(self, username, password, soft_id):
            self.username = username
            self.password = md5(password.encode('utf-8')).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 post_pic(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 report_error(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()
    

    loginweibo.py:登录新浪微博,需要加一个获取Cookies的方法,并针对不同的情况返回不同的结果

    import requests
    from requests import RequestException
    from selenium import webdriver
    from selenium.common.exceptions import NoSuchElementException, TimeoutException
    from selenium.webdriver.common.by import By
    from selenium.webdriver.support.ui import WebDriverWait
    from selenium.webdriver.support import expected_conditions as EC
    from CookiesPool.chaojiying import Chaojiying
     
    # 超级鹰用户名、密码、软件ID、
    CHAOJIYING_USERNAME = ''
    CHAOJIYING_PASSWORD = ''
    CHAOJIYING_SOFT_ID = 
    CHAOJIYING_KIND = 1902
     
    class LoginWeibo():
        def __init__(self,username,password,browser):
            self.url = 'https://www.weibo.com'
            self.browser = browser
            self.wait = WebDriverWait(self.browser,20)
            self.username = username
            self.password = password
            self.chaojiying = Chaojiying(CHAOJIYING_USERNAME, CHAOJIYING_PASSWORD, CHAOJIYING_SOFT_ID)
     
        # def __del__(self):
        #     self.browser.close()
     
        def open(self):
            """
            打开网页输入用户名密码
            :return: None
            """
            self.browser.get(self.url)
            #找到用户名和密码输入框
            '''
            <input id="loginname" type="text" class="W_input" maxlength="128" autocomplete="off" action-data="text=邮箱/会员帐号/手机号" action-type="text_copy" name="username" node-type="username" tabindex="1">
            '''
            username = self.wait.until(EC.presence_of_element_located((By.ID,'loginname')))
            '''
            <input type="password" class="W_input" maxlength="24" autocomplete="off" value="" action-type="text_copy" name="password" node-type="password" tabindex="2">
            '''
            password = self.wait.until(EC.presence_of_element_located((By.NAME,'password')))
            #输入用户名和密码
            username.send_keys(self.username)
            password.send_keys(self.password)
     
        def get_click_button(self):
            '''
            找到登录按钮
            :return:
            '''
     
            '''
            <a href="javascript:void(0)" class="W_btn_a btn_32px " action-type="btn_submit" node-type="submitBtn" suda-data="key=tblog_weibologin3&amp;value=click_sign" tabindex="6"><span node-type="submitStates">登录</span></a>
            '''
            button = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME,'W_btn_a')))
            return  button
     
        def login_successfully(self):
            """
            判断登陆是否成功
            :return:
            """
            '''
            登录成功才能看到
            <em class="W_ficon ficon_mail S_ficon">I</em>
            '''
            try:
                return bool(
                    WebDriverWait(self.browser,5).until(EC.presence_of_element_located((By.CSS_SELECTOR,'.ficon_mail')))
                )
            except TimeoutException:
                return  False
     
        def get_click_image(self,name='captcha.png'):
            """
            获取验证码图片
            :param name:
            :return: 图片对象
            """
            try:
                '''
                <img width="95" height="34" action-type="btn_change_verifycode" node-type="verifycode_image" src="https://login.sina.com.cn/cgi/pin.php?r=88815771&amp;s=0&amp;p=gz-66c0488ef9191010d88bea8c9f3a09fdf3bf">
                '''
                element = self.wait.until(EC.presence_of_element_located((By.XPATH,'//img[@action-type="btn_change_verifycode"]')))
                image_url = element.get_attribute('src')
                image = get_html(image_url).content
                with open(name, 'wb') as f:
                    f.write(image)
                return image
            except NoSuchElementException:
                print('')
            return  None
     
        def password_error(self):
            """
            判断是否密码错误
            :return:
            """
            try:
                element = WebDriverWait(self.browser, 5).until(
                    EC.presence_of_element_located((By.XPATH, '//span[@node-type="text"]')))
                print(element.text)
                if element.text == '用户名或密码错误。查看帮助':
                    return True
            except TimeoutException:
                return False
     
        def get_cookies(self):
            """
            获取Cookies
            :return:
            """
            print(self.browser.get_cookies())
            return self.browser.get_cookies()
     
        def login(self):
            #打开网址 输入用户名和密码
            self.open()
            # 点击登录按钮
            button = self.get_click_button()
            button.click()
            if self.password_error():
                print('用户名或密码错误')
                return {
                    'status': 2,
                    'content': '用户名或密码错误'
                }
            if self.login_successfully():
                print('登录成功')
                #获取帐号对应的cookies
                cookies = self.get_cookies()
                return {
                    'status': 1,
                    'content': cookies
                }
            else: #有时会需要验证码
                # 获取验证码图片
                image = self.get_click_image()
                # 识别验证码
                result = self.chaojiying.post_pic(image, CHAOJIYING_KIND)
                print(result)
                # 输入验证码
                verifycode = self.wait.until(EC.presence_of_element_located((By.NAME, 'verifycode')))
                verifycode.send_keys(result['pic_str'])
                # 点击登录按钮
                button = self.get_click_button()
                button.click()
                if self.login_successfully():
                    print('登录成功')
                    # 获取帐号对应的cookies
                    cookies = self.get_cookies()
                    return {
                        'status': 1,
                        'content': cookies
                    }
                else:
                    self.chaojiying.report_error(result['pic_id'])
                    self.login()
                    # return {
                    #     'status': 3,
                    #     'content': '登录失败'
                    # }
     
    def get_html(url):
        try:
            # 添加User-Agent,放在headers中,伪装成浏览器
            headers = {
                'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36'
            }
            response = requests.get(url, headers=headers)
            if response.status_code == 200:
                response.encoding = response.apparent_encoding
                return response
            return None
        except RequestException:
            return None
     
    if __name__ == '__main__':
        result = LoginWeibo('','',webdriver.Chrome()).login()
    

    loginzhihu.py:使用requests模拟登录微博,登录成功后,获取帐号对应的cookies

    import requests
    import re
    import execjs
    import time
    import hmac
    from hashlib import sha1
     
     
    class Zhihu(object):
     
        def __init__(self, username, password):
     
            self.username = username
            self.password = password
            self.session = requests.session()
            self.headers = {
                'content-type': 'application/x-www-form-urlencoded',
                'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36',
                'x-zse-83': '3_1.1'
            }
     
        def login(self):
     
            # 请求login_url,udid_url,captcha_url加载所需要的cookie
            login_url = 'https://www.zhihu.com/signup?next=/'
            resp = self.session.get(login_url, headers=self.headers)
            print("请求{},响应状态码:{}".format(login_url, resp.status_code))
            # print(self.session.cookies.get_dict())
            # self.save_file('login',resp.text)
     
            udid_url = 'https://www.zhihu.com/udid'
            resp = self.session.post(udid_url, headers=self.headers)
            print("请求{},响应状态码:{}".format(udid_url, resp.status_code))
            # print(self.session.cookies.get_dict())
     
            captcha_url = 'https://www.zhihu.com/api/v3/oauth/captcha?lang=en'
            resp = self.session.get(captcha_url, headers=self.headers)
            print("请求{},响应状态码:{}".format(captcha_url, resp.status_code))
            # print(self.session.cookies.get_dict())
            # print(resp.text)
            # self.save_file('captcha',resp.text)
     
            # 校验是否需要验证吗,需要则直接退出,还没遇到过需要验证码的
            if re.search('true', resp.text):
                print('需要验证码')
                exit()
     
            # 获取signature参数
            self.time_str = str(int(time.time() * 1000))
            signature = self.get_signature()
            # print(signature)
     
            # 拼接需要加密的字符串
            string = "client_id=c3cef7c66a1843f8b3a9e6a1e3160e20&grant_type=password&timestamp={}&source=com.zhihu.web&signature={}&username={}&password={}&captcha=&lang=en&ref_source=homepage&utm_source=".format(
                self.time_str, signature, self.username, self.password)
            # print(string)
            # 加密字符串
            encrypt_string = self.encrypt(string)
            # print(encrypt_string)
     
            # post请求登陆接口
            post_url = "https://www.zhihu.com/api/v3/oauth/sign_in"
            resp = self.session.post(post_url, data=encrypt_string, headers=self.headers)
            print("请求{},响应状态码:{}".format(post_url, resp.status_code))
            print(self.session.cookies.get_dict())
            # print(resp.text)
            # self.save_file('post', resp.text)
     
            # 校验是否登陆成功
            if re.search('user_id', resp.text):
                print('登陆成功')
                return {
                    'status': 1,
                    'content': self.session.cookies.get_dict()
                }
            else:
                print("登陆失败")
                return {
                    'status': 2,
                    'content': "登陆失败"
                }
     
        def test(self):
     
            # 请求个人信息接口查看个人信息
            me_url = 'https://www.zhihu.com/api/v4/me'
            data = {
                'include': 'ad_type;available_message_types,default_notifications_count,follow_notifications_count,vote_thank_notifications_count,messages_count;draft_count;following_question_count;account_status,is_bind_phone,is_force_renamed,email,renamed_fullname;ad_type'
            }
            resp = self.session.get(me_url, data=data, headers=self.headers)
            print("请求{},响应状态码:{}".format(me_url, resp.status_code))
            print(resp.text)
            return resp.status_code
            # self.save_file('me',resp.text)
     
        def encrypt(self, string):
            with open('zhihu.js', 'r', encoding='utf-8') as f:
                js = f.read()
            result = execjs.compile(js).call('encrypt', string)
            return result
     
        def get_signature(self):
     
            h = hmac.new(key='d1b964811afb40118a12068ff74a12f4'.encode('utf-8'), digestmod=sha1)
            grant_type = 'password'
            client_id = 'c3cef7c66a1843f8b3a9e6a1e3160e20'
            source = 'com.zhihu.web'
            now = self.time_str
            h.update((grant_type + client_id + source + now).encode('utf-8'))
            return h.hexdigest()
     
        def save_file(self, name, html):
     
            with open('{}.html'.format(name), 'w', encoding='utf-8') as f:
                f.write(html)
     
     
    if __name__ == "__main__":
        account = Zhihu('', '')
        account.login()
        account.test()
    

    知识点:cookies
    根据Netscape公司的规定,Cookie格式如下:

    Set-Cookie: NAME=VALUE;Expires=DATE;Path=PATH;Domain=DOMAIN_NAME;SECURE
    
    NAME=VALUE:
      这是每一个Cookie均必须有的部分。NAME是该Cookie的名称,VALUE是该Cookie的值。在字符串“NAME=VALUE”中,不含分号、逗号和空格等字符。
    
    Expires=DATE:Expires
      变量是一个只写变量,它确定了Cookie有效终止日期。该属性值DATE必须以特定的格式来书写:星期几,DD-MM-YY HH:MM:SS GMT,GMT表示这是格林尼治时间。反之,不以这样的格式来书写,系统将无法识别。该变量可省,如果缺省时,则Cookie的属性值不会保存在用户的硬盘中,而仅仅保存在内存当中,Cookie文件将随着浏览器的关闭而自动消失。
    
    Domain=DOMAIN-NAME:Domain
      该变量是一个只写变量,它确定了哪些Internet域中的Web服务器可读取浏览器所存取的Cookie,即只有来自这个域的页面才可以使用Cookie中的信息。这项设置是可选的,如果缺省时,设置Cookie的属性值为该Web服务器的域名。
    
    Path=PATH:Path
      属性定义了Web服务器上哪些路径下的页面可获取服务器设置的Cookie。一般如果用户输入的URL中的路径部分从第一个字符开始包含Path属性所定义的字符串,浏览器就认为通过检查。如果Path属性的值为“/”,则Web服务器上所有的WWW资源均可读取该Cookie。同样该项设置是可选的,如果缺省时,则Path的属性值为Web服务器传给浏览器的资源的路径名。可以看出我们借助对Domain和Path两个变量的设置,即可有效地控制Cookie文件被访问的范围。
    
    Secure:
    在Cookie中标记该变量,表明只有当浏览器和Web Server之间的通信协议为加密认证协议时,浏览器才向服务器提交相应的Cookie。当前这种协议只有一种,即为HTTPS。
    

    在模拟登陆成功后获得cookies,把cookies字典的name和value提取出来,重新赋值给dict字典,以便后续存储在redis的cookies_weibo中,存储的时候采用set方法,传入采用json.dumps()序列化之后的cookies

    def process_cookies(self, cookies):
        dict = {}
        for cookie in cookies:
            dict[cookie['name']] = cookie['value']
        return dict
    

    3. 检测模块

    我们现在可以用生成模块来生成Cookies,但还是免不了Cookies失效的问题,例如时间太长导致Cookies失效,或者Cookies使用太频繁导致无法正常请求网页。如果遇到这样的Cookies,我们肯定不能让它继续保存在数据库里。
      所以我们还需要增加一个定时检测模块,它负责遍历池中的所有Cookies,同时设置好对应的检测链接,我们用一个个Cookies去请求这个链接。如果请求成功,或者状态码合法,那么该Cookies有效;如果请求失败,或者无法获取正常的数据,比如直接跳回登录页面或者跳到验证页面,那么此Cookies无效,我们需要将该Cookies从数据库中移除。
      此Cookies移除之后,刚才所说的生成模块就会检测到Cookies的Hash和账号的Hash相比少了此账号的Cookies,生成模块就会认为这个账号还没生成Cookies,那么就会用此账号重新登录,此账号的Cookies又被重新更新。

    定时检测数据库中的Cookies 能否正常登录。检测模块需要做的就是检测Cookies失效,然后将其从数据中移除。tester.py:

    import json
    import re
    import requests
    from requests.exceptions import ConnectionError
    from CookiesPool.cookiespool.db import *
    from CookiesPool.cookiespool.config import *
    
    class ValidTester:
        def __init__(self,website):
            self.website = website
            self.accounts_db = RedisClient("counts",self.website)
            self.cookies_db = RedisClient("cookies", self.website)
    
        def test(self,username,cookies):
            '''
            如果要求其子类一定要实现,不实现的时候会导致问题,那么采用raise的方式就很好。
            而此时产生的问题分类是NotImplementedError。
            :return:
            '''
            raise NotImplementedError
    
        def run(self):
            cookies_group =  self.cookies_db.all()
            for username,cookies in cookies_group.item():
                self.test(username,cookies)
    
    
    class WeiboValidTester(ValidTester):
    
        def __init__(self,website="weibo"):                     # 指定website为微博
            ValidTester.__init__(self,website)
            # super().__init__(website)
    
        def test(self,username,cookies):
            print('正在测试Cookies', '用户名', username)
            try:
                cookies = json.loads(cookies)
            except:
                print('Cookies不合法', username)
                self.cookies_db.delete(username)
                print('删除Cookies', username)
                return
            try:
                url = TEST_URL_MAP[self.website]        # 从字典里面取出weibo等对应的url,注意取的时候用字典的形式
                response = requests.get(url=url,cookies=cookies,timeout=5,allow_redirects=False)
                if response.status_code == 200:
                    html = response.text
                    ret = re.findall("'islogin']='1'",html)
                    if ret:
                        print('Cookies有效', username)
                    else:
                        print('Cookies失效', username)
                        self.cookies_db.delete(username)
                        print('删除Cookies', username)
                else:
                    print('Cookies失效', username)
                    self.cookies_db.delete(username)
                    print('删除Cookies', username)
    
            except ConnectionError as e:                # 那就是链接异常
                print('发生异常', e.args)
    
    
    class ZhihuValidTester(ValidTester):
    
        def __init__(self,website="zhihu"):                     # 指定website为知乎
            ValidTester.__init__(self,website)
            # super().__init__(website)
    
        def test(self,username,cookies):
            print('正在测试Cookies', '用户名', username)
            try:
                cookies = json.loads(cookies)
            except:
                print('Cookies不合法', username)
                self.cookies_db.delete(username)
                print('删除Cookies', username)
                return
            try:
                url = TEST_URL_MAP[self.website]        # 从字典里面取出weibo等对应的url,注意取的时候用字典的形式
                response = requests.get(url=url,cookies=cookies,timeout=5,allow_redirects=False)
                if response.status_code == 200:
                    html = response.text
                    ret = re.findall("'islogin']='1'",html)
                    if ret:
                        print('Cookies有效', username)
                    else:
                        print('Cookies失效', username)
                        self.cookies_db.delete(username)
                        print('删除Cookies', username)
                else:
                    print('Cookies失效', username)
                    self.cookies_db.delete(username)
                    print('删除Cookies', username)
    
            except ConnectionError as e:                # 那就是链接异常
                print('发生异常', e.args)
    
    if __name__ == '__main__':
        WeiboValidTester().run()
    

    代码解析:
    (1)父类
      为了实现通用可扩展性,我们首先定义一个检测器的父类,声明一些通用组件,实现如下所示:

    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)
    

    在这里定义了一个父类叫作ValidTester,在init()方法里指定好站点的名称website,另外建立两个存储模块连接对象cookies_db和accounts_db,分别负责操作Cookies和accounts的Hash,run()方法是入口,在这里是遍历了所有的Cookies,然后调用test()方法进行测试,在这里test()方法是没有实现的,也就是说我们需要写一个子类来重写这个test()方法。

    知识点1:****dic.items()

    用于提取字典种的key和value值,并将key和它对应的value用元组包围起来,返回值的类型是 dict_items

    dic = {'name': 'helen', 'age': 18, 'sex': 'women'}
    li = list(dic.items())
    print(li)                   # [('name', 'helen'), ('age', 18), ('sex', 'women')]
    

    知识点2:
      Python编程中raise可以实现报出错误的功能,而报错的条件可以由程序员自己去定制。在面向对象编程中,可以先预留一个方法接口不实现,在其子类中实现。如果要求其子类一定要实现,不实现的时候会导致问题,那么采用raise的方式就很好。而此时产生的问题分类是NotImplementedError。

    (2)子类
      每个子类负责各自不同网站的检测,如检测微博的就可以定义为WeiboValidTester,实现其独有的test()方法来检测微博的Cookies是否合法,然后做相应的处理,所以在这里我们还需要再加一个子类来继承这个ValidTester,重写其test()方法,实现如下:

    import json
    import requests
    from requests.exceptions import ConnectionError
    
    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)
                    print('部分测试结果', response.text[0:50])
                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)
    

    首先,在初始化时,继承父类的init(), 有如下2种方式

    class Person(Animal):
        def __init__(self, name, hp, ad, sex):
            Animal.__init__(self, name, hp, ad)         # 执行父类方法,需要self
            super().__init__(name, hp, ad)            # 简写.效果同上。它不需要传参数Person,self。因为它本来就在类里面,自动获取参数
            self.sex = sex
    

    然后,test()方法。
    第一步:将Cookies转化为字典,检测Cookies的格式,如果格式不正确,直接将其删除。

    json.loads()反序列化,读取。(和dumps相反,loads函数则是将json格式的数据解码,转换为Python字典)

    import json
    
    dic = [1,['a','b','c'],3,{'k1':'v1','k2':'v2'}]
    ret = json.dumps(dic,ensure_ascii=False)    # 序列化时,不使用ascii码
    res = json.loads(ret)                       # 反序列化
    print(type(ret),ret)                        # <class 'str'> [1, ["a", "b", "c"], 3, {"k1": "v1", "k2": "v2"}]
    print(type(res),res)                        # <class 'list'> [1, ['a', 'b', 'c'], 3, {'k1': 'v1', 'k2': 'v2'}]
    
    

    第二步:1.如果格式没问题,那么就拿此Cookies使用requests模块请求被检测的URL。(检测的URL可以是某个Ajax接口,为了实现可配置化,我们将测试URL也定义成字典)。对微博来说,我们用Cookies去请求目标站点,同时禁止重定向和设置超时时间
        2.得到Response之后检测其返回状态码。如果直接返回200状态码,则Cookies有效,否则可能遇到了302跳转等情况,一般会跳转到登录页面,则Cookies已失效。如果Cookies失效,我们将其从Cookies的Hash里移除即可。状态码检测完成之后,还可以通过检测某个标志性的东西,是否存在于新页面。进一步做判断。

    4. 接口模块

    生成模块和检测模块如果定时运行就可以完成Cookies实时检测和更新。但是Cookies最终还是需要给爬虫来用,同时一个Cookies池可供多个爬虫使用,所以我们还需要定义一个Web接口,爬虫访问此接口便可以取到随机的Cookies。我们采用Flask来实现接口的搭建。提供对外服务的接口。api.py:

    接口模块主要分成以下几部分:
    1、导入flask,实例化一个Flask对象,在主程序中开启flask服务

    from flask import Flask, g
    
    __all__ = ['app']
    app = Flask(__name__)
    
    if __name__ == '__main__':
        app.run(host=API_HOST, port=API_PORT)
    

    2、通过flask的g对象,使用反射,为flask设置属性,也就是链接redis实例化出我们的cookies_weibo数据库,方便后面调用set()、random()、counts()方法

    def get_conn():
    
        for website in GENERATOR_MAP:
            print(website)
            if not hasattr(g, website):
                setattr(g, website + '_cookies', eval('RedisClient' + '("cookies", "' + website + '")'))
                setattr(g, website + '_accounts', eval('RedisClient' + '("accounts", "' + website + '")'))
        return g
    

    3、通过flask的路由,建立几个视图函数。调用cookies_weibo数据库的set()、random()、counts()方法

    完整代码

    import json
    from flask import Flask, g
    from CookiesPool.cookiespool.config import *
    from CookiesPool.cookiespool.db import *
    
    __all__ = ['app']
    
    app = Flask(__name__)
    
    
    @app.route('/')
    def index():
        return '<h2>Welcome to Cookie Pool System</h2>'
    
    
    def get_conn():
        """
        获取
        :return:
        """
        for website in GENERATOR_MAP:
            print(website)
            if not hasattr(g, website):
                setattr(g, website + '_cookies', eval('RedisClient' + '("cookies", "' + website + '")'))
                setattr(g, website + '_accounts', eval('RedisClient' + '("accounts", "' + website + '")'))
        return g
    
    
    @app.route('/<website>/random')
    def random(website):
        """
        获取随机的Cookie, 访问地址如 /weibo/random
        :return: 随机Cookie
        """
        g = get_conn()
        cookies = getattr(g, website + '_cookies').random()
        return cookies
    
    
    @app.route('/<website>/add/<username>/<password>')
    def add(website, username, password):
        """
        添加用户, 访问地址如 /weibo/add/user/password
        :param website: 站点
        :param username: 用户名
        :param password: 密码
        :return:
        """
        g = get_conn()
        print(username, password)
        getattr(g, website + '_accounts').set(username, password)
        return json.dumps({'status': '1'})
    
    
    @app.route('/<website>/count')
    def count(website):
        """
        获取Cookies总数
        """
        g = get_conn()
        count = getattr(g, website + '_cookies').count()
        return json.dumps({'status': '1', 'count': count})
    
    
    if __name__ == '__main__':
        app.run(host=API_HOST, port=API_PORT)
    

    我们同样需要实现通用的配置来对接不同的站点,所以接口链接的第一个字段定义为站点名称,第二个字段定义为获取的方法,例如,/weibo/random是获取微博的随机Cookies,/zhihu/random是获取知乎的随机Cookies。

    知识点1:

    from flask import Flask
    
    app = Flask(__name__)
    
    @app.route('/')
    def hello_world():
        return 'Hello World!'
    
    if __name__ == '__main__':
        app.run(host='0.0.0.0',port=9000)
    
    • 第1行,引入Flask类,Flask类实现了一个WSGI应用
    • 第3行,app是Flask的实例,它接收包或者模块的名字作为参数,但一般都是传递name。让flask.helpers.get_root_path函数通过传入这个名字确定程序的根目录,以便获得静态文件和模板文件的目录。
    • 第5~7行,使用app.route装饰器会将URL和执行的视图函数的关系保存到app.url_map属性上。处理URL和视图函数的关系的程序就是路由,这里的视图函数就是hello_world。
    • 第9行,使用这个判断可以保证当其他文件引用这个文件的时候(例如“from hello import app”)不会执行这个判断内的代码,也就是不会执行app.run函数。
    • 第10行,执行app.run就可以启动服务了。默认Flask只监听虚拟机的本地127.0.0.1这个地址,端口为5000。 而我们对虚拟机做的端口转发端口是9000,所以需要制定host和port参数,0.0.0.0表示监听所有地址,这样就可以在本机访问了。 服务器启动后,会调用werkzeug.serving.run_simple进入轮询,默认使用单进程单线程的werkzeug.serving.BaseWSGIServer处理请求, 实际上还是使用标准库BaseHTTPServer.HTTPServer,通过select.select做0.5秒的“while TRUE”的事件轮询。 当我们访问“http://127.0.0.1:9000/”,通过app.url_map找到注册的“/”这个URL模式,就找到了对应的hello_world函数执行,返回“hello world!”,状态码为200。 如果访问一个不存在的路径,如访问“http://127.0.0.1:9000/a”,Flask找不到对应的模式,就会向浏览器返回“Not Found”,状态码为404'''

    要想明白“@app.route()”的工作原理,先看一看Python中的装饰器(就是以“@”开头的)。理解了装饰器的概念,再继续来看路由:

    什么是路由:处理URL和函数之间关系的程序。简单的说:路由就是将URL绑定到一个函数上面,这样浏览器客户端向web服务器发送一个URL请求后,服务器中的路由收到这个URL后,能立马找到对应的函数进行处理。

    以下三个路由的例子:

    @app.route('/')def index():
        pass 
    
    @app.route('/<username>')
    def show_user(username):
        pass 
    
    @app.route('/post/<int:post_id>')
    def show_post(post_id):
        pass
    
    • 第1个路由:捕获到 “/”,就进入函数进行处理,里面的函数叫做视图函数。URL从左到右第一个“/”为止一般是首页,因此第1一个路由是处理首页。
    • 第2个路由:捕获到 “/<username>” ,其中尖括号<....> 这一部分是动态可变的部分,其中不可以带有斜杠的。而且这个动态部分当做参数传递给了视图函数。
    • 第3个路由:捕获到 “/post/<int:post_id>” 其中 “/post/”是静态部分,“<int:post_id>”是动态部分,但是这个动态部分必须是个int类型的整数,同样其中不可以带有斜杠。同样这个动态部分也成为参数传入了视图函数show_post。这个rule是 <converter:name>的形式,而converter除了有int,还有float,path,string。

    注意本例中add视图函数,传参规则

    @app.route('/<website>/add/<username>/<password>')
    def add(website, username, password):
        """
        添加用户, 访问地址如 /weibo/add/user/password
    
        """
        g = get_conn()
        print(username, password)
        getattr(g, website + '_accounts').set(username, password)
        return json.dumps({'status': '1'})
    

    还有一些需要注意的URL规则:

    • 1.如果路由中的URL规则是有斜杠结尾的,但是用户请求的时候URL结尾没有斜杠,则会自动将用户重定向到带有斜杠的页面。
    • 2.如果路由中的URL规则结尾不带斜杠的,但是用户请求时带了斜杠,那么就会返回404错误响应。
    • 3.还可以为同一个视图函数,定义多个URL规则:

    这指定/users/将是第1页的URL,并且 /users/page/N将是第N页的URL

    知识点2:
    for 直接循环一个字典时,下面这种情形,出来的结果只有key,没有value

    # 如果需要查询多个key,请使用for循环
    dic = {'name': 'summer', 'age': 21}
    for i in dic:
        print(i)
    
    '''
    执行输出:
    age
    Name
    '''
    

    知识点3:反射的应用

    hasattr(obj, attr):
      这个方法用于检查obj是否有一个名为attr的值的属性,返回一个布尔值。
    getattr(obj, attr):
      调用这个方法将返回obj中名为attr值的属性的值,例如如果attr为'bar',则返回obj.bar。
    setattr(obj, attr, val):
      调用这个方法将给obj的名为attr的值的属性赋值为val。例如如果attr为'bar',则相当于obj.bar = val
    delattr(obj, name)
      该函数删除该obj的一个由string指定的属性。delattr(x, 'foobar')=del x.foobar
    

    在g对象中3次使用反射

    # 检查g对象是否有
    if not hasattr(g, website):
    
    
    # 为g对象设置属性,实例化cookies_weibo这个db
    setattr(g, website + "_cookies", eval('ReidsClient' + '("cookies",website)'))
    
    # 在获取cookies_weibo这个对象的cookies没有使用常规的obj.random(),而是使用getattr()方法获得
    cookies = getattr(g,website + "_cookies").random()    # 注意写法,g的website_weibo是g的一个属性,同时它也是一个对象,对象调用random()方法
    

    知识点4:eval()的调用
      eval() 函数用来执行一个字符串表达式,并返回表达式的值。

    setattr(g, "accounts_"+website,eval('ReidsClient' + '("accounts",website)'))
    

    这里是想使用eval方法实例化一个accounts_weibo对象,ReidisClient是类,实例化时给init()初始化函数传参,也就是之前的type和website

    注意:

    • eval函数里面是字符串,所以类名外面要加字符串形式;
    • 字符串形式的类名使用加号+拼接带字符串的括号;
    • 括号里面传参里面传参,如果是确定的字符串就传字符串形式的参数,如果是变量,就传变量,反正括号()外有引号包围,整体还是字符串;
    • website作为变量,以下2种形式等同,建议第一种;
    website = "weibo"
    
    
    arg1 = eval('("accounts",website)')
    arg2 = eval('("accounts", "' + website + '")')
    
    print(arg1,type(arg1))
    print(arg2,type(arg2))
    
    
    '''
    结果输出:
    ('accounts', 'weibo') <class 'tuple'>
    ('accounts', 'weibo') <class 'tuple'>
    '''
    

    5. 调度模块

    最后,我们再加一个调度模块让这几个模块配合运行起来,主要的工作就是驱动几个模块定时运行,同时各个模块需要在不同进程上运行。

    思想:
    1.定义一个Scheduler类。里面有一个run方法,是它的主脚本;除此之外还有3个方法generator、tester、api,将generator和tester的map字典进行遍历,同时利用eval()动态新建各个类的对象,调用其入口run()方法运行各个模块;

    2.在run()主脚本里,先使用if判断在配置文件中是否开启执行generator、tester、api的开关。如果开启了,就在本模块里通过多进程的方式,使它们各自调用起来。

    scheduler.py

    import time
    from  multiprocessing import Process                # 注意process和Process都有,要首字母大写的
    from CookiesPool.cookiespool.api import app         # api模块只引入了app
    from CookiesPool.cookiespool.config import *        # 其他模块是*
    from CookiesPool.cookiespool.generator import *
    from CookiesPool.cookiespool.tester import *
    
    
    class Scheduler(object):
        @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)
    
        @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)
    
    
        @staticmethod
        def api():
            app.run(host=API_HOST, port=API_PORT)
    
        def run(self):
    
            if GENERATOR_PROCESS:                               # 等价于if GENERATOR_PROCESS == True:
                generate_process = Process(target=Scheduler.generate_cookie)
                generate_process.start()
    
            if VALID_PROCESS:
                valid_process = Process(target=Scheduler.valid_cookie)
                valid_process.start()
    
            if API_PROCESS:
                api_process = Process(target=Scheduler.api)
                api_process.start()
    
    
    
    if __name__ == '__main__':
        scheduler = Scheduler()
        scheduler.run()
    

    知识点1:进程
    同时,各个模块的多进程使用了multiprocessing中的Process类,调用其start()方法即可启动各个进程。

    p.start()
    启动进程,并自动调用该子进程中的p.run()
    
    p.run()
    进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法
    
    p.join([timeout])
    主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。timeout是可选的超时时间,需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程  
    

    另外,注意target后跟的是函数名,不加括号

    知识点2:eval的使用

    • 首先,for循环map里有哪些网站;
    • 然后,使用eval()实例化tester对象。因为cls是变量,所以没有加引号,后面跟着括号,里面放着参数,第一个website因为是固定的实例化时的变量,所以在括号内没有加引号,而第二个按理来说应该是一个字符串,但是例子中又是一个实实在在的变量,为了做区分,就加了两个引号,并且左右2个加号。
    for website, cls in TESTER_MAP.items():
        tester = eval(cls + '(website="' + website + '")')
        tester.run()
        print('Cookies检测完成')
        del tester:
    

    至此,我们的Cookies就全部完成了。

    相关文章

      网友评论

          本文标题:Python爬虫 | cookie池

          本文链接:https://www.haomeiwen.com/subject/psnwihtx.html