以POST方式抓取資料-政府電子採購網

星期一 22 Jul 2019   even  
教學

動態網站經常會在頁面中設置表單,讓使用者可以勾拉點選,或手動輸入,設定你所要的條件,按下提交後,會經由伺服器後台運算,將符合你所設定條件的結果回傳給你。表單將使用者的變數回傳給後台透過兩個方式,一為之前介紹過的GET,另一為今天的主題POST。這次的主題就是要教各位如何撰寫python的爬蟲程式抓取以POST傳遞資料的網站,目標為政府電子採購網



REF: 關於POST和GET的差異,詳細可以參考這篇文章:

淺談 HTTP Method:表單中的 GET 與 POST 有什麼差別?

目的

政府所有的標案都會上網公開在政府採購網上,然而這個網站的查詢系統非常的瑣碎,因此我們在此嘗試編寫簡單的python爬蟲程式,只要輸入關鍵字與日期,就能將查詢結果的機關名稱、標案名稱、預算金額、決標金額、是否決標、公告日期、開標日期、決標日期等資訊漂亮的以excel表格抓取下來。這樣的程式可以有非常多的後續應用。


觀察網站結構

首先要先查看對方伺服器是怎麼運作的,先實際以瀏覽器到目標網站實際操作,觀察網址以及檢查網站。

在表單區中可以設定要查詢的參數群,然後有個查詢按鈕,按下後就得到查詢的結果。

按下查詢後,結果再下面一一列出來,首先看一下網址,完全沒有改變,因此這是以POST的方式來傳遞參數給後台,這是檢查是POST還是GET的最簡單方法。

以POST的方式會將所需的參數群藏起來,而不是直接在網址中攤開給你看,不過只要檢查一下就可以立刻看到所需的參數群。如何檢查可以查看我的上一篇教學。總之,你可以很快地看到他所需的參數群。

這些數值就是我們之後在編寫爬蟲程式時必須給予的參數群。他的參數全包含: tmpQuery、SentencetimeRange、querySentence、tenderStatusType、sortCol、DATEtimeRangeTemp、sym、itemPerPage。


繼續來觀察這個網站的結構,我們將檔案條件設為決標,查詢出來的結果如下:

在檔案名稱的地方有超連結可以點進去,可以獲得細部的資訊。只要將這些超連結抓取下來,之後再以GET的方式一一進入這些網址,將我們想要抓的資料抓取下來,就可以完成了。

接下來測試如何進入不同的分頁,在下方有不同的分頁可以選取,試著點選下一頁看看。

網址後面多出了一大串參數群!!所以這個網站同時以GET和POST兩種的方式傳遞資料囉?進行檢查來查看一下:

確實是GET!! 參數群和POST一模一樣,但是多了一項似乎是代表分頁頁碼的參數,d-7095067-p。不知為何,這個網站的設計者同時允許使用GET或POST的方式來查詢後台資料。另外我們可以嘗試看看一次傳送的資料筆數是否有上限,如此我們就不用重複進不同分頁抓取資料,這裡可以在實際編寫程式時測試看看。

OK~現在整個爬取的策略已經擬好了,可以開始測試以及編寫程式碼了。

測試拜訪

request.post來獲得網站資訊,使用時要放上參數群,如下:

timeRangec和timeRangeTemp似乎是指查詢的日期範圍,tenderStatusType是標案的種類,querySentence是關鍵字,itemPerPage是每分頁幾筆資料。

import requests

url = 'https://web.pcc.gov.tw/prkms/prms-searchBulletionClient.do?root=tps'

data = {'tmpQuerySentence': None,
'timeRange':'108/1/1-108/12/31',
'querySentence':'高雄市政府',
'tenderStatusType':'決標',
'sortCol':'TENDER_NOTICE_DATE',
'timeRangeTemp':'108/1/1-108/12/31',
'sym':'on',
'itemPerPage':'10'}

r = reuqests.post(url, data)
print(r.ok)
print(r.is_redirect)

True
False

使用.ok以及.is_redirect檢查一下是否一切正常,看起來非常順利,完全沒有遇到任何阻礙。

接著,剖析這個頁面,以beautifulsoup、re等抓取特定的元素,看看能不能抓出這10筆資料的機關名稱、標案名稱、招標日期、決標日期。一樣使用瀏覽器,檢查元素,查看我們所要抓的內容再藏在哪段html的原始碼內,再依此設計適當的程式碼去抓取。

我這裡以re(正規表達式),設計多組文字比對的樣板,將符合樣板的內文抓取下來,你們可以嘗試使用別種方式。

PS: 這個網站似乎更新很快,網頁原始碼在兩個禮拜內就進行過更新,導致我兩週前寫的程式碼無效,需要再重新設計,因此各位在使用我的程式碼時不保證會work。

import re

patterns = [
        'style="width:18%;text-align:left">(.*?)</td>',
        '<div class="wordwrap">(.*?)</div>',
        '</a>[\s\S]*?width:9%;text-align:left;min-width:8%;">(.*?)</td>',
        '"width:15%;text-align:left;min-width:40px;">([\s\S]*?)</td>',
        'href="(.*?pkAtmMain.*?)"'
        ]

agency_name = re.findall(patterns[0], r.text)  # 機關名稱
project_name = re.findall(patterns[1], r.text)  # 標案名稱
project_announce_date = re.findall(patterns[2], r.text)  #標案公告日期
FF_date_t = re.findall(patterns[3], r.text)  
FF_date = [re.findall('\d\d\d\/\d\d\/\d\d',i)[0] for i in FF_date_t] # 決標或無法決標日期
Success = ['無法決標' not in i for i in FF_date_t] # 是否得標
Detail_url = re.findall(patterns[4], r.text) # 詳細頁面的網址

預算金額和得標金額要顯示細節的網站中才看的到,因此要再進一步地拜訪detail url裡面的頁面,但是當你嘗試以迴圈的方式爬取時,很快就會被偵測並阻攔,而導致無法獲取資料。如下:

detail_content = []
for b, d_url in zip(Success, Detail_url):
    if b:
        r = requests.get('https://web.pcc.gov.tw' + d_url)
        if len(r.history) > 0:
            print(f'You are redirected to: {r.url}')
            break
        detail_content.append(r.text)
        time.sleep(1)

You are redirected to: http://web.pcc.gov.tw/tps/tpam/validate.do?id=ll2f9iHfd4FhO4s2jGJHVmAHcxLhpj

你會被導向到如下的網頁,必須手動驗證才能進入查看標案細部的資訊,因此暫時是無法以簡單的爬蟲程式去獲取預算金額。

要突破任何驗證都是非常瑣碎的,我想到的方法是使用虛擬瀏覽器selenium,外加上opencv進行影像辨識,及pyautogui自動控制鍵盤滑鼠的方式來突破驗證,這部分進階的操作之後有機會再介紹。這篇教學的目的只是在於讓各位知道如何使用POST而已,因此我就在這裡結尾,很抱歉無法提供簡單的爬取預算金額算決標金額的方法。

最後,讓我將程式碼統整成簡單好用的函式,可以直接輸出漂亮的excel檔,以及為本篇教學做個總結吧~

結尾

這篇教學介紹了如何判斷網站是以POST或GET的方式傳遞資料,並介紹了如何找到參數群,以及如何使用requests.post去抓取資料,雖然不如一開始預期的,可以很簡單的抓取到細部的預算金額和決標金額等項目,但這就是撰寫爬蟲程式經常會碰到的狀況,當你覺得很順利可以一路爬下去時,就經常會碰到一些想像不到的狀況來阻攔,這就是人生啊。

一樣,感謝您閱讀到最後,希望這篇文章有讓你多了解一點如何進行網路爬蟲,有機會下次見囉~

本章節程式碼

import requests
import re
import pandas as pd

def GetTenderData(start_date = '108/1/1', end_date = '108/12/31', keyword = '高雄市政府', max_items = 100):
   
    url = 'https://web.pcc.gov.tw/prkms/prms-searchBulletionClient.do?root=tps'
    
    data = {'tmpQuerySentence': None,
    'timeRange':f'{start_date}-{end_date}',
    'querySentence':f'{keyword}',
    'tenderStatusType':'決標',
    'sortCol':'TENDER_NOTICE_DATE',
    'timeRangeTemp':f'{start_date}-{end_date}',
    'd-7095067-p':'1',
    'sym':'on',
    'itemPerPage':f'{max_items}'}
    
    r = requests.post(url,data = data)
    
    
    patterns = [
            'style="width:18%;text-align:left">(.*?)</td>',
            '<div class="wordwrap">(.*?)</div>',
            '</a>[\s\S]*?width:9%;text-align:left;min-width:8%;">(.*?)</td>',
            '"width:15%;text-align:left;min-width:40px;">([\s\S]*?)</td>',
            'href="(.*?pkAtmMain.*?)"'
            ]
    
    agency_name = re.findall(patterns[0], r.text)
    project_name = re.findall(patterns[1], r.text)
    project_announce_date = re.findall(patterns[2], r.text)
    FF_date_t = re.findall(patterns[3], r.text)
    FF_date = [re.findall('\d\d\d\/\d\d\/\d\d',i)[0] for i in FF_date_t]
    Success = ['無法決標' not in i for i in FF_date_t]
    Detail_url = ['https://web.pcc.gov.tw' + u for u in re.findall(patterns[4], r.text)]
    
    col_names = ['機關名稱','標案名稱','標案公告日期','決標或無法決標日期','是否決標?','標案網址']
    
    df = pd.DataFrame([agency_name, project_name, project_announce_date,FF_date,Success,Detail_url]).T
    df.columns = col_names
    df.to_excel('政府電子採購網資料.xlsx',index=False)
python 網頁爬蟲教學
python網路爬蟲簡介
python網路爬蟲基本工具(1)
python網路爬蟲教學-實戰篇(1) 蘋果日報馬網
使用偽裝user-agent爬取蝦皮購物網
撈取深網中的資料-蝦皮購物API
以POST方式抓取資料-政府電子採購網
python網路爬蟲教學-Selenium基本操作
python網路爬蟲應用-facebook社團成員參與度分析

相關文章:

>