用PYTHON初次編寫(xiě)小工具心得

用PYTHON初次編寫(xiě)小工具心得

背景

有一個(gè)朋友拜托我開(kāi)發(fā)一個(gè)搶票類的工具迫悠,剛好最近有看python3的書(shū)籍鹏漆,順便練下手便答應(yīng)了她。題外話:是某公司CRM系統(tǒng)中的客戶預(yù)約功能创泄,購(gòu)買額度200萬(wàn)以下的金融產(chǎn)品很不容易預(yù)約上(而500萬(wàn)的產(chǎn)品不需要搶)艺玲,全國(guó)每個(gè)產(chǎn)品也就幾個(gè)名額。由于我朋友去到公司不久(新人)200萬(wàn)以下的產(chǎn)品是她收入和業(yè)績(jī)的主要來(lái)源了验烧。
寫(xiě)下本文的目的也僅僅是把涉及到各方面的要點(diǎn)記錄(踩過(guò)的坑)下來(lái)板驳,希望能幫助到初學(xué)者。

  1. selenium中switch_to()的使用
  2. requests中如何模擬登錄用戶
  3. selenium + requests 實(shí)現(xiàn)無(wú)所不能操作
  4. pyqt與qml文件通訊
  5. UI卡死與多線程

開(kāi)發(fā)迭代過(guò)程

第一版:龜速自動(dòng)操作——selenium

一開(kāi)始覺(jué)得不就是個(gè)拼手速的工具嘛碍拆,于是使用了selenium來(lái)模擬人的操作若治,工具很快寫(xiě)完,剛好100行代碼感混。遇到過(guò)的坑:

  • 由于網(wǎng)頁(yè)中采用了frameset結(jié)構(gòu)端幼,采用switch_to()方法,需要注意相對(duì)位置弧满。
 iframes = self.driver.driver.find_elements_by_tag_name('iframe')
 iframe1 = iframes[1]
 print('獲取預(yù)約頁(yè)面地址:' + iframe1.get_attribute('src'))
 self.driver.driver.switch_to.frame(iframe1)  # 切換到產(chǎn)品預(yù)約頁(yè)iframe
  • 解決xss引起的chrome報(bào)錯(cuò)
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)

在實(shí)際搶的過(guò)程中婆跑,卻還是沒(méi)有搶到,雖然是比人快了不少庭呜,看來(lái)需要加速滑进,讓我想到了requests犀忱。

第二版:極速手動(dòng)操作——requests

不出所料,速度還是很快扶关,不過(guò)由于此CRM系統(tǒng)的阴汇,對(duì)其他行業(yè)的人來(lái)講根本操作不來(lái)(需通過(guò)審查元素獲取cookies、產(chǎn)品搜索與預(yù)約產(chǎn)品的url等)

# 準(zhǔn)備搜索
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('正在進(jìn)行第(%d)次搜索...' % search_count)
    try:
        results = product_json['results']
        if results > 1:
            print('錯(cuò)誤:查出多條產(chǎn)品节槐,請(qǐng)退出后重新輸入產(chǎn)品名稱')
            break

        elif results == 1: # 找到產(chǎn)品
            proudct_id =  product_json['records'][0]['id']
            proudct_CPJC =  product_json['records'][0]['CPJC']
            print('>>>找到預(yù)約產(chǎn)品:id:'+proudct_id+'CPJC:'+proudct_CPJC)
            break
        elif results == 0:
            # 循環(huán)讀取
            time.sleep(0.001)
    except json.decoder.JSONDecodeError as e:
        print('參數(shù)不正確')
        exit(0);

但由于此CRM系統(tǒng)的URL也是動(dòng)態(tài)的搀庶,含有操作碼oprateId(各個(gè)頁(yè)面還不同,且動(dòng)態(tài)改變铜异,沒(méi)找到規(guī)律)哥倔,只能從審查元素中去找到對(duì)應(yīng)的URL和模擬header等信息(很短時(shí)間才有效)。另外不可能每次我來(lái)幫她搶啊,于是就有了selenium+requests的想法

第三版:無(wú)所不能的組合——selenium+requests

在搜索產(chǎn)品和提交預(yù)約之前通過(guò)selenium獲取cookies和頁(yè)面地址上的operateId和token揍庄。

通過(guò)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)
        

通過(guò)selenium的switch_to()獲取頁(yè)面地址上的operateId和token

iframe = self.driver.driver.find_element_by_tag_name('iframe')
self.driver.driver.switch_to.frame(iframe)  # 切換到主頁(yè)下半部iframe
self.driver.click_link_by_text("產(chǎn)品預(yù)約")
time.sleep(1)
self.driver.driver.switch_to.parent_frame()
iframes = self.driver.driver.find_elements_by_tag_name('iframe')
iframe1 = iframes[1]
# print('獲取預(yù)約頁(yè)面地址:' + iframe1.get_attribute('src'))
self.driver.driver.switch_to.frame(iframe1)  # 切換到產(chǎn)品預(yù)約頁(yè)iframe

self.driver.click_link_by_id('ext-gen32')  # 點(diǎn)開(kāi)搜索頁(yè)
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('獲取預(yù)約頁(yè)面地址:' + 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參數(shù)
token = dict_query['Token']
operateid = dict_query['OperateID']
self.Token = token
# self.SearchOperateID = operateid
self.YuyueOperateID = operateid
print('獲取Token:' + self.Token)
print('獲取預(yù)約頁(yè)面操作碼:' + self.YuyueOperateID)

此版很接近完美的實(shí)現(xiàn)了自動(dòng)化的登錄(驗(yàn)收碼還是需要手動(dòng)收入)咆蒿、自動(dòng)搜索產(chǎn)品,當(dāng)放出產(chǎn)品的時(shí)候自動(dòng)預(yù)約蚂子。經(jīng)測(cè)試4個(gè)產(chǎn)品全部預(yù)約到蜡秽。不過(guò)登錄用戶名、密碼缆镣、客戶手機(jī)、預(yù)約金額试浙、以及準(zhǔn)備預(yù)約的產(chǎn)品都寫(xiě)在python文件中的董瞻。讓她改幾個(gè)字(居然說(shuō)是讓她寫(xiě)代碼,我服了)田巴,本來(lái)想通過(guò)一個(gè)配置文件解決钠糊。但想到python的UI操作還沒(méi)試過(guò)(很久很久以前用過(guò)C語(yǔ)言+GTK),于是想試下pyqt+QT creator(模版只想可視化操作的)壹哺。

第四版:把程序裝進(jìn)殼里——PYQT+Qt Creator

Qt Creator 的設(shè)計(jì)目標(biāo)是使開(kāi)發(fā)人員能夠利用 Qt 這個(gè)應(yīng)用程序框架更加快速及輕易的完成開(kāi)發(fā)任務(wù)抄伍。Qt Creator 包括項(xiàng)目生成向?qū)А⒏呒?jí)的 C++ 代碼編輯器管宵、瀏覽文件及類的工具截珍、集成了 Qt Designer、Qt Assistant箩朴、Qt Linguist岗喉、圖形化的 GDB 調(diào)試前端,集成 qmake 構(gòu)建工具等炸庞。(百度百科)
Qt Creator 可以創(chuàng)建多種工程钱床,我選擇的是qml文件,很快做好了qml文件埠居,但是如何與python通訊呢查牌?百度了下都沒(méi)找到多少系統(tǒng)的文章事期,官方教程也沒(méi)講到(英文)https://doc.qt.io/qtforpython/tutorials/index.html

  1. 在界面觸發(fā)事件調(diào)用python, 需在python中用pyqtSlot()申明為槽函數(shù) ,并設(shè)置上下文關(guān)聯(lián)纸颜,這樣qml文件中就可以直接調(diào)用兽泣。
@pyqtSlot() # qml中可調(diào)用
def begin(self):
    #代碼略
    pass

if __name__ == '__main__':

app = QGuiApplication(sys.argv)
qml = QQmlApplicationEngine('ui.qml')
rootObject = qml.rootObjects()[0]

instance = Reserve(qml.rootContext() ,rootObject)   #預(yù)約實(shí)例
qml.rootContext().setContextProperty('con',instance ) #與qml文件建立關(guān)聯(lián)

sys.exit(app.exec())

qml文件中綁定事件,觸發(fā)調(diào)用python的槽函數(shù)

Connections {
    target: button_start
    onClicked: con.begin()
}
  1. 如何主動(dòng)更改ui界面中的值呢懂衩?例如之前用的print打印日志撞叨,現(xiàn)在需要全部顯示到界面上
    在qml文件中自定義方法,類似javascript
function updatelog(log) {// 定義函數(shù)
    textArea.append(log)
}
function clearlog() {
    textArea.clear()
}

然后可直接

 def __init__(self,context,parent=None):
    super(Reserve,self).__init__(parent)
    self.win = parent
    self.ctx = context
    
 #可直接調(diào)用qml中方法
self.win.showlog("測(cè)試日志")

好啦浊洞,界面也有了牵敷,與程序也封裝好了,開(kāi)始搶票吧法希,怎么回事枷餐?界面卡死了。之前學(xué)習(xí)時(shí)候知道需要將界面與程序使用線程分開(kāi)苫亦。
首先創(chuàng)建一個(gè)線程類

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()
    #設(shè)置
    def setup(self, instance):
        self.yuyue = instance

    #內(nèi)部/外部(線程)使用的輸出信息
    def log(self,message):
        self.signal.emit(message)

    def run(self):
        self.yuyue.config()
        self.yuyue.login()
        self.yuyue.start()
        # 執(zhí)行完畢后發(fā)出信號(hào)
        self.log("運(yùn)行完畢")

在主程序Init_方法中毛肋,啟動(dòng)線程,這里兩個(gè)信號(hào)(signal)屋剑,一個(gè)用于打印日志润匙,一個(gè)用于清空日志(日志達(dá)到某個(gè)閥值)

     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報(bào)錯(cuò)。
        self.driver_name = 'chrome'
        self.driver = Browser(driver_name=self.driver_name,chrome_options=chrome_opt)

        #啟動(dòng)一個(gè)線程唉匾,并設(shè)置連接通道
        self.thread = WorkThread()
        self.thread.signal.connect(self.callbacklog)
        self.thread.clearsignal.connect(self.callbackclear)
        #向通道發(fā)送信息
        self.thread.log("初始化完成")
        # 保存session
        self.s = requests.session()

    # 槽函數(shù)(通道末端)
    def callbacklog(self, log):
        self.win.updatelog(log)  # 調(diào)用qml中的方法
        pass

    def callbackclear(self):
        self.win.clearlog()  # 調(diào)用qml中的方法
        pass

這樣線程原來(lái)使用print()打印日志的方法全部替換成self.thread.log()即可孕讳。運(yùn)行界面一點(diǎn)都不卡了。

結(jié)束語(yǔ):對(duì)python新手來(lái)說(shuō)涉及到面還不少巍膘,雖然不少坑收獲還是不少厂财。我在身邊很多朋友眼中一直都是大神一樣的存在(其實(shí)我知道這些都是小把戲),我朋友在完成第二版的時(shí)候說(shuō)過(guò)一句話讓我很欣慰:

你把我的夢(mèng)境變成現(xiàn)實(shí)了峡懈,太厲害了璃饱。

當(dāng)然還有可以改進(jìn)的地方,比如驗(yàn)證碼完全可以不用手動(dòng)輸入肪康,可以利用機(jī)器學(xué)習(xí)荚恶,對(duì)驗(yàn)證碼進(jìn)行訓(xùn)練,然后自動(dòng)識(shí)別梅鹦。不過(guò)真的沒(méi)必要了裆甩。就她一個(gè)人用確實(shí)沒(méi)必要折騰了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末齐唆,一起剝皮案震驚了整個(gè)濱河市嗤栓,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖茉帅,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件叨叙,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡堪澎,警方通過(guò)查閱死者的電腦和手機(jī)擂错,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)樱蛤,“玉大人钮呀,你說(shuō)我怎么就攤上這事∽蚍玻” “怎么了爽醋?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)便脊。 經(jīng)常有香客問(wèn)我蚂四,道長(zhǎng),這世上最難降的妖魔是什么哪痰? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任遂赠,我火速辦了婚禮,結(jié)果婚禮上晌杰,老公的妹妹穿的比我還像新娘跷睦。我一直安慰自己,他們只是感情好肋演,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布送讲。 她就那樣靜靜地躺著,像睡著了一般惋啃。 火紅的嫁衣襯著肌膚如雪指煎。 梳的紋絲不亂的頭發(fā)上雕旨,一...
    開(kāi)封第一講書(shū)人閱讀 51,754評(píng)論 1 307
  • 那天慨畸,我揣著相機(jī)與錄音吃衅,去河邊找鬼果覆。 笑死沾谜,一個(gè)胖子當(dāng)著我的面吹牛闭树,可吹牛的內(nèi)容都是我干的朴爬。 我是一名探鬼主播扣癣,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼惰帽,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了父虑?” 一聲冷哼從身側(cè)響起该酗,我...
    開(kāi)封第一講書(shū)人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后呜魄,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體悔叽,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年爵嗅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了娇澎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡睹晒,死狀恐怖趟庄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情伪很,我是刑警寧澤戚啥,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站是掰,受9級(jí)特大地震影響虑鼎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜键痛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一炫彩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧絮短,春花似錦江兢、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至席里,卻和暖如春叔磷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背奖磁。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工改基, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人咖为。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓秕狰,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親躁染。 傳聞我的和親對(duì)象是個(gè)殘疾皇子鸣哀,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 文/洛小簡(jiǎn) 昨夜和一個(gè)認(rèn)識(shí)不久的女性朋友聊到了一個(gè)有趣的話題我衬,她說(shuō)她很久不去廚房了,不喜歡油煙、不喜歡油漬低飒、不喜歡...
    洛小簡(jiǎn)閱讀 444評(píng)論 3 0
  • 此刻许昨,我再一次從那位跛足大叔的身旁經(jīng)過(guò)。 那是一個(gè)六年前我還是大一新生時(shí)參加學(xué)腥焐蓿“香榭麗社”法語(yǔ)社團(tuán)公開(kāi)課地遙遠(yuǎn)的...
    琰然一夢(mèng)閱讀 498評(píng)論 0 1
  • 在我老家有這樣一句話“外婆疼外孫拌喉,冷水洗腳跟”速那。這句話什么意思呢?腳跟是什么地方尿背?那是一塊皮厚端仰,污垢多的地方,冷水...
    葉糖糖閱讀 11,026評(píng)論 34 40
  • 一座不大不小的體育館田藐,被無(wú)盡的汗水和喧囂充盈荔烧。來(lái)來(lái)回回的跑步聲摩擦著地面,仿佛迸出著幾朵運(yùn)動(dòng)的火花汽久,幾聲蓋過(guò)地面的...
    甲申紀(jì)事dcl閱讀 1,299評(píng)論 3 2