美文网首页
用PYTHON初次编写小工具心得

用PYTHON初次编写小工具心得

作者: WhatsCode | 来源:发表于2019-04-15 10:05 被阅读0次

    用PYTHON初次编写小工具心得

    背景

    有一个朋友拜托我开发一个抢票类的工具,刚好最近有看python3的书籍,顺便练下手便答应了她。题外话:是某公司CRM系统中的客户预约功能,购买额度200万以下的金融产品很不容易预约上(而500万的产品不需要抢),全国每个产品也就几个名额。由于我朋友去到公司不久(新人)200万以下的产品是她收入和业绩的主要来源了。
    写下本文的目的也仅仅是把涉及到各方面的要点记录(踩过的坑)下来,希望能帮助到初学者。

    1. selenium中switch_to()的使用
    2. requests中如何模拟登录用户
    3. selenium + requests 实现无所不能操作
    4. pyqt与qml文件通讯
    5. UI卡死与多线程

    开发迭代过程

    第一版:龟速自动操作——selenium

    一开始觉得不就是个拼手速的工具嘛,于是使用了selenium来模拟人的操作,工具很快写完,刚好100行代码。遇到过的坑:

    • 由于网页中采用了frameset结构,采用switch_to()方法,需要注意相对位置。
     iframes = self.driver.driver.find_elements_by_tag_name('iframe')
     iframe1 = iframes[1]
     print('获取预约页面地址:' + iframe1.get_attribute('src'))
     self.driver.driver.switch_to.frame(iframe1)  # 切换到产品预约页iframe
    
    • 解决xss引起的chrome报错
    chrome_opt = Options()
    chrome_opt.add_argument('--disable-xss-auditor')  
    self.driver_name = 'chrome'
    self.driver = Browser(driver_name=self.driver_name,chrome_options=chrome_opt)
    

    在实际抢的过程中,却还是没有抢到,虽然是比人快了不少,看来需要加速,让我想到了requests。

    第二版:极速手动操作——requests

    不出所料,速度还是很快,不过由于此CRM系统的,对其他行业的人来讲根本操作不来(需通过审查元素获取cookies、产品搜索与预约产品的url等)

    # 准备搜索
    postdata = {
        'start': '0',
        'Search': '1',
        'Key': product_name,
        'loadStore': 'true',
        'extResponse':  'true',
    }
    headers={
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:65.0) Gecko/20100101 Firefox/65.0',
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
        'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
        'Accept-Encoding': 'gzip, deflate',
        'Connection': 'keep-alive',
        'X-Requested-With': 'XMLHttpRequest',
        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
        'Cookie': header_cookies
    }
    search_count = 0
    while True:
        rep = s.post(search_prod, data=postdata, headers=headers)
    
        product_json = json.loads(rep.text)
        search_count = search_count + 1
        if search_count % 10 == 1:
            print('正在进行第(%d)次搜索...' % search_count)
        try:
            results = product_json['results']
            if results > 1:
                print('错误:查出多条产品,请退出后重新输入产品名称')
                break
    
            elif results == 1: # 找到产品
                proudct_id =  product_json['records'][0]['id']
                proudct_CPJC =  product_json['records'][0]['CPJC']
                print('>>>找到预约产品:id:'+proudct_id+'CPJC:'+proudct_CPJC)
                break
            elif results == 0:
                # 循环读取
                time.sleep(0.001)
        except json.decoder.JSONDecodeError as e:
            print('参数不正确')
            exit(0);
    

    但由于此CRM系统的URL也是动态的,含有操作码oprateId(各个页面还不同,且动态改变,没找到规律),只能从审查元素中去找到对应的URL和模拟header等信息(很短时间才有效)。另外不可能每次我来帮她抢啊,于是就有了selenium+requests的想法

    第三版:无所不能的组合——selenium+requests

    在搜索产品和提交预约之前通过selenium获取cookies和页面地址上的operateId和token。

    通过selenium的get_cookies()获取cookies

    cookies = self.driver.driver.get_cookies()
    for cookie in cookies:
        if cookie['name'] == 'JSESSIONID':
            self.jsessionid = cookie['value']
            break
    
    print('cookie信息:')
    print('jsessionid:' + self.jsessionid)
            
    

    通过selenium的switch_to()获取页面地址上的operateId和token

    iframe = self.driver.driver.find_element_by_tag_name('iframe')
    self.driver.driver.switch_to.frame(iframe)  # 切换到主页下半部iframe
    self.driver.click_link_by_text("产品预约")
    time.sleep(1)
    self.driver.driver.switch_to.parent_frame()
    iframes = self.driver.driver.find_elements_by_tag_name('iframe')
    iframe1 = iframes[1]
    # print('获取预约页面地址:' + iframe1.get_attribute('src'))
    self.driver.driver.switch_to.frame(iframe1)  # 切换到产品预约页iframe
    
    self.driver.click_link_by_id('ext-gen32')  # 点开搜索页
    time.sleep(1)
    self.driver.driver.switch_to.parent_frame()
    iframes = self.driver.driver.find_elements_by_tag_name('iframe')
    iframe2 = iframes[2]
    search_url = iframe2.get_attribute('src')
    # print('获取预约页面地址:' + search_url)
    parsed_search_url = urllib.parse.urlparse(search_url)
    # print(parsed_search_url)
    query_str = parsed_search_url.query
    query_parms = query_str.split('&')
    
    dict_query = self._parseQuery(query_parms)  # 处理url参数
    token = dict_query['Token']
    operateid = dict_query['OperateID']
    self.Token = token
    # self.SearchOperateID = operateid
    self.YuyueOperateID = operateid
    print('获取Token:' + self.Token)
    print('获取预约页面操作码:' + self.YuyueOperateID)
    

    此版很接近完美的实现了自动化的登录(验收码还是需要手动收入)、自动搜索产品,当放出产品的时候自动预约。经测试4个产品全部预约到。不过登录用户名、密码、客户手机、预约金额、以及准备预约的产品都写在python文件中的。让她改几个字(居然说是让她写代码,我服了),本来想通过一个配置文件解决。但想到python的UI操作还没试过(很久很久以前用过C语言+GTK),于是想试下pyqt+QT creator(模版只想可视化操作的)。

    第四版:把程序装进壳里——PYQT+Qt Creator

    Qt Creator 的设计目标是使开发人员能够利用 Qt 这个应用程序框架更加快速及轻易的完成开发任务。Qt Creator 包括项目生成向导、高级的 C++ 代码编辑器、浏览文件及类的工具、集成了 Qt Designer、Qt Assistant、Qt Linguist、图形化的 GDB 调试前端,集成 qmake 构建工具等。(百度百科)
    Qt Creator 可以创建多种工程,我选择的是qml文件,很快做好了qml文件,但是如何与python通讯呢?百度了下都没找到多少系统的文章,官方教程也没讲到(英文)https://doc.qt.io/qtforpython/tutorials/index.html

    1. 在界面触发事件调用python, 需在python中用pyqtSlot()申明为槽函数 ,并设置上下文关联,这样qml文件中就可以直接调用。
    @pyqtSlot() # qml中可调用
    def begin(self):
        #代码略
        pass
    
    if __name__ == '__main__':
    
    app = QGuiApplication(sys.argv)
    qml = QQmlApplicationEngine('ui.qml')
    rootObject = qml.rootObjects()[0]
    
    instance = Reserve(qml.rootContext() ,rootObject)   #预约实例
    qml.rootContext().setContextProperty('con',instance ) #与qml文件建立关联
    
    sys.exit(app.exec())
    

    qml文件中绑定事件,触发调用python的槽函数

    Connections {
        target: button_start
        onClicked: con.begin()
    }
    
    1. 如何主动更改ui界面中的值呢?例如之前用的print打印日志,现在需要全部显示到界面上
      在qml文件中自定义方法,类似javascript
    function updatelog(log) {// 定义函数
        textArea.append(log)
    }
    function clearlog() {
        textArea.clear()
    }
    

    然后可直接

     def __init__(self,context,parent=None):
        super(Reserve,self).__init__(parent)
        self.win = parent
        self.ctx = context
        
     #可直接调用qml中方法
    self.win.showlog("测试日志")
    

    好啦,界面也有了,与程序也封装好了,开始抢票吧,怎么回事?界面卡死了。之前学习时候知道需要将界面与程序使用线程分开。
    首先创建一个线程类

    class WorkThread(QThread):
        signal = pyqtSignal(type(""))
        clearsignal = pyqtSignal()
        message=""
        yuyue=""
    
        def __int__(self,parent=None):
            super(WorkThread,self).__init__(parent)
    
        def __del__(self):
            self.wait()
        #设置
        def setup(self, instance):
            self.yuyue = instance
    
        #内部/外部(线程)使用的输出信息
        def log(self,message):
            self.signal.emit(message)
    
        def run(self):
            self.yuyue.config()
            self.yuyue.login()
            self.yuyue.start()
            # 执行完毕后发出信号
            self.log("运行完毕")
    

    在主程序Init_方法中,启动线程,这里两个信号(signal),一个用于打印日志,一个用于清空日志(日志达到某个阀值)

         def __init__(self,context,parent=None):
            super(Reserve,self).__init__(parent)
            self.win = parent
            self.ctx = context
            
            chrome_opt = Options()
            chrome_opt.add_argument('--disable-xss-auditor')  # 解决xss引起的chrome报错。
            self.driver_name = 'chrome'
            self.driver = Browser(driver_name=self.driver_name,chrome_options=chrome_opt)
    
            #启动一个线程,并设置连接通道
            self.thread = WorkThread()
            self.thread.signal.connect(self.callbacklog)
            self.thread.clearsignal.connect(self.callbackclear)
            #向通道发送信息
            self.thread.log("初始化完成")
            # 保存session
            self.s = requests.session()
    
        # 槽函数(通道末端)
        def callbacklog(self, log):
            self.win.updatelog(log)  # 调用qml中的方法
            pass
    
        def callbackclear(self):
            self.win.clearlog()  # 调用qml中的方法
            pass
    

    这样线程原来使用print()打印日志的方法全部替换成self.thread.log()即可。运行界面一点都不卡了。

    结束语:对python新手来说涉及到面还不少,虽然不少坑收获还是不少。我在身边很多朋友眼中一直都是大神一样的存在(其实我知道这些都是小把戏),我朋友在完成第二版的时候说过一句话让我很欣慰:

    你把我的梦境变成现实了,太厉害了。

    当然还有可以改进的地方,比如验证码完全可以不用手动输入,可以利用机器学习,对验证码进行训练,然后自动识别。不过真的没必要了。就她一个人用确实没必要折腾了。

    相关文章

      网友评论

          本文标题:用PYTHON初次编写小工具心得

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