需求是催生項目和推進項目的不竭動力。
背景:
最近,因為媳婦要做個B超檢查,想着去大醫院查查應該更放心,所以就把目標瞄准在A醫院。早已耳聞A院一號難求萬人空巷,所以把所有能接觸到的機會都看了一遍,線下聽傳聞說早上徐亞5點左右去排隊還未必能排上,線上主要有以下兩個來源:
1.支付寶
在支付寶的城市服務中,定位到指定城市,是能夠看一些醫院提供了預約掛號接口的,顯然A醫院當之無愧也在其中。
簡便易用的支付寶用戶體驗,即便是第一次來也好像是經常使用這項服務般熟練。找到A院,搜索婦產科,在list中有若干醫生,有的標注無號源,有的標注可預約,大抵如下:
於是滿懷激動的點擊"可預約",可是彈出來的卻不是我想要的結果--!
后來聽到諸如凌晨12點會有號放出,但是最終也是收到同樣冷冰冰的彈出窗口。對於用此招掛上號的我深表佩服,只是拋開這個不說,感覺A院在支付寶這塊的投入太低,UI設計也很呆板,最主要的是搶不到號。
2.百度醫生
相對來說,百度醫生要比上面做的實誠的多,有就是有,沒有就是沒有。不管是PC端還是app端,用戶界面更加柔和smooth。
於是我把希望寄托在這里,但是A院的號難搶,這是事實。而百度醫生我覺得還有一塊空白可以實現的就是監控機制,好比12306可以刷票一樣,添加這個模塊,相信app的下載量和使用量會提高一個"當量"。
很顯然,這個功能,還沒有,那我只能自己動手了~~~
我的思路
1. 百度從醫院拿數據,那我就從百度拿數據。我能夠監控百度醫生放出來的消息,就基本與醫院同步了;
2. 我要使用的是百度醫生,但是這個需要登錄,而且登錄方式只有使用手機號+驗證碼的方式。這個方法可行,但是需要中間sleep以輸入驗證碼,或者是調用短信接口,顯然這兩個都不是一個很好的途徑,時間金錢成本高,用戶體驗差;
3. 於是我想到,可以先登錄百度賬戶(用戶名+密碼),然后利用這個賬戶進入百度醫生,就免去了短信驗證這個環節。
碼前預熱
1. 框架選擇
毫無疑問,我們采用selenium,一款web測試應用工具,模擬我在瀏覽器上的操作。可以基於IE、FF、Chrome等等瀏覽器,實現啟動關閉瀏覽器/頁面,在頁面上點擊、定位元素等相應操作。關於selenium webdriver的背景知識就不做介紹,一篇博文顯然難以闡述清楚。
2. 工具使用
在使用selenium過程中,頁面元素的定位是個核心問題。我們可以使用By類下面的By.name,By.id,By.linkText分別獲取獲取name屬性,id屬性,超鏈接文本。這些方式的定位我們直接可以在DOM結構中看出來,其中比較復雜的是xpath,需要根據DOM結構實現定位,這時候我們需要一個工具可以實時的測試我們的xpath表達式是否能夠正確定位到指定元素。
a.在Firefox瀏覽器中,我們可以安裝插件FirePath
這里我們通過字符串".//*[@id='su']"就能夠定位到"百度一下"這個按鈕
b.在chrome里面,我們可以下載插件XPath Helper,有關它的用戶,請參看
http://www.chromein.com/crx_11654.html
思路有了,工具齊了,那就開始着手做吧
1.啟動chrome瀏覽器
需要下載chrome的驅動
System.setProperty ( "webdriver.chrome.driver" , "C:\\Users\\Administrator\\Downloads\\chromedriver_win32\\chromedriver.exe" ); WebDriver driver = new ChromeDriver();
2.定位元素執行動作
如定位百度頁面的登錄按鈕,並執行點擊操作
WebElement loginLink = driver.findElement(By.xpath(".//*[@id='u1']/a[7]")); loginLink.click();
3.等待頁面加載完成
有時候在driver.get()到某個頁面時,如果頁面沒有加載完成,這時候去定位元素容易拋出異常,所以需要加入等待頁面加載完成的功能。這里將其封裝在一個函數中:
public static void waitForLoad(WebDriver driver) { ExpectedCondition<Boolean> pageLoad= new ExpectedCondition<Boolean>() { public Boolean apply(WebDriver driver) { return ((JavascriptExecutor)driver).executeScript("return document.readyState").equals("complete"); } }; WebDriverWait wait = new WebDriverWait(driver, 30); wait.until(pageLoad); }
4.遇到異常重新啟動機制
監控的原理在於間歇性的點擊相應的科室並監聽頁面中是否有想要的元素出現(如出現預約掛號),如果出現則點擊進入相應界面,如果沒有出現則一直監聽。
但是在實測過程中,發現持續點擊某科室會出現頁面一直加載的情況,這時候會拋出異常,程序無法正常進行。
所以在這里捕獲異常,通過遞歸調用的思想,關閉先前的driver對象,並調用自己重新生成一份監聽driver對象,從而保證程序正常執行,提高了程序的健壯性。
try { WebDriverWait wait = new WebDriverWait(driver,7000); wait.until(new ExpectedCondition<WebElement>(){ public WebElement apply(WebDriver d) { try { Thread.sleep(2000);//為避免給baidu早成麻煩,每2秒監聽一次 } catch (InterruptedException e) { e.printStackTrace(); } departLink.click();//departLink為婦產科的文本鏈接 monitoringTimes++;//監聽的次數 System.out.println("第" + monitoringTimes + "次監控"); return d.findElement(By.xpath("//*[@id='doctor-info-list']/descendant::*[contains(text(), '預約掛號')][1]")); } }); waitForLoad(driver); } catch (Exception e) { ticket = true; System.out.println("抱歉,目前余號不足,請稍后再試"); }finally{ if(ticket){ driver.close(); startMonitor();//遞歸調用 } }
5.成果展示
但從程序來說,可以現實自動搶號功能,已成功預約到非婦產科以外科室的號。
運行到監控的頁面並監聽:
實時監聽過程:
難點和展望
- dom結構變動,會導致無法正常定位;
- 代碼執行過程,有時會拋出異常,需要處理;
- 頻繁的登錄,需要在登錄界面輸入驗證碼,驗證碼的識別可以采用OCR破解;
- 最好做成一個客戶端,提供給用戶自定義輸入醫院和科室乃至指定時間段或醫生
寫此程序,純屬需求催生,僅為一樂,勿動邪念^^。
如果您覺得閱讀本文對您有幫助,請點一下“推薦”按鈕,您的“推薦”將是我最大的寫作動力!如果您想持續關注我的文章,請掃描二維碼,關注JackieZheng的微信公眾號,我會將我的文章推送給您,並和您一起分享我日常閱讀過的優質文章。