實(shí)戰(zhàn):抓取山東新高考選考科目要求數(shù)據(jù))
2020年山東新高考第一年落地后高校專(zhuān)業(yè)的選考科目要求成了考生和家長(zhǎng)最關(guān)心的數(shù)據(jù)之一。當(dāng)時(shí)招考信息網(wǎng)站提供了一個(gè)在線查詢(xún)系統(tǒng)按學(xué)校逐頁(yè)展示專(zhuān)業(yè)類(lèi)的選考要求但頁(yè)面沒(méi)有導(dǎo)出功能翻上幾百頁(yè)復(fù)制粘貼顯然是體力活。我接到一個(gè)整理全量數(shù)據(jù)的小任務(wù)把擬在山東招生的普通高校專(zhuān)業(yè)類(lèi)選考科目要求全部抓下來(lái)做成一張能篩選的本地表格。文章里沒(méi)有復(fù)雜的框架就是Python加requests加BeautifulSoup的一整套實(shí)戰(zhàn)記錄適合剛接觸爬蟲(chóng)、想拿真實(shí)站點(diǎn)練手的朋友參考。1. 需求拆解選考科目要求數(shù)據(jù)到底長(zhǎng)什么樣1.1 先理解“選考科目要求”的業(yè)務(wù)含義山東新高考實(shí)行“33”模式除了語(yǔ)數(shù)外三門(mén)必考考生要從思想政治、歷史、地理、物理、化學(xué)、生物六門(mén)里選三門(mén)參加等級(jí)考。而高校在招生時(shí)會(huì)針對(duì)每個(gè)專(zhuān)業(yè)類(lèi)提出選考科目要求。常見(jiàn)的有幾種形式不限選科、必須選考物理、化學(xué)和生物均須選考、物理或化學(xué)任選一門(mén)等。這直接決定考生能不能報(bào)某個(gè)專(zhuān)業(yè)所以數(shù)據(jù)本身的準(zhǔn)確性特別關(guān)鍵。爬蟲(chóng)爬下來(lái)之后如果字段錯(cuò)位、漏行、專(zhuān)業(yè)名稱(chēng)被截?cái)嗄钦沓鰜?lái)的表就沒(méi)法用。做這個(gè)項(xiàng)目第一步不是寫(xiě)代碼而是把手上的資料先理清楚需要保存哪些字段、頁(yè)面上的展示規(guī)則是什么、選考科目要求有哪些固定寫(xiě)法。我當(dāng)時(shí)列了六個(gè)核心字段分別是年份、院校代碼、院校名稱(chēng)、專(zhuān)業(yè)類(lèi)名稱(chēng)、選考科目要求、備注。年份單獨(dú)拎出來(lái)是因?yàn)椴樵?xún)系統(tǒng)里可能默認(rèn)展示當(dāng)年數(shù)據(jù)保存下來(lái)方便以后做多年度對(duì)比。備注字段不是每個(gè)頁(yè)面都有但有些專(zhuān)業(yè)會(huì)寫(xiě)明“按大類(lèi)招生”或“其中某方向有特殊要求”這種補(bǔ)充信息不能丟。1.2 觀察頁(yè)面結(jié)構(gòu)先花十分鐘省掉兩小時(shí)拿到查詢(xún)地址后我沒(méi)急著寫(xiě)爬蟲(chóng)而是先用瀏覽器打開(kāi)頁(yè)面按F12打開(kāi)開(kāi)發(fā)者工具看兩個(gè)東西頁(yè)面上的表格結(jié)構(gòu)以及翻頁(yè)時(shí)發(fā)送的請(qǐng)求。這類(lèi)查詢(xún)系統(tǒng)分為幾種典型形態(tài)。第一種是表格數(shù)據(jù)直接渲染在HTML里翻頁(yè)鏈接就是帶page參數(shù)的普通網(wǎng)頁(yè)地址這種最簡(jiǎn)單。第二種是頁(yè)面先給一個(gè)框架數(shù)據(jù)通過(guò)AJAX接口返回JSON再動(dòng)態(tài)填充到表格這種就比解析HTML麻煩一些但接口返回的數(shù)據(jù)往往更規(guī)整。第三種是點(diǎn)擊查詢(xún)后才出數(shù)據(jù)而且用的是POST請(qǐng)求參數(shù)藏在請(qǐng)求體里需要從網(wǎng)絡(luò)面板里找Form Data。2020年這個(gè)查詢(xún)系統(tǒng)屬于第一種和第二種混合的形態(tài)首屏能看到表格但翻頁(yè)是通過(guò)一個(gè)固定地址帶page和pageSize參數(shù)完成的。我在Network面板里選中XHR和Doc兩個(gè)過(guò)濾條件刷新頁(yè)面后找到真實(shí)請(qǐng)求確認(rèn)返回的是HTML片段而不是JSON。既然返回的是HTML就直接用BeautifulSoup解析比處理嵌套JSON還直觀。2. 動(dòng)手前的準(zhǔn)備技術(shù)選型與字段規(guī)劃2.1 為什么是requests加BeautifulSoup而不是Scrapy做這個(gè)爬蟲(chóng)之前我順手評(píng)估過(guò)工具。Scrapy功能確實(shí)強(qiáng)自帶并發(fā)調(diào)度、去重、中間件適合做大規(guī)模分布式采集。但這個(gè)任務(wù)的數(shù)據(jù)量頂多幾千條記錄、幾十個(gè)頁(yè)面用Scrapy有點(diǎn)小題大做而且調(diào)試起來(lái)不如腳本直觀。requests負(fù)責(zé)發(fā)HTTP請(qǐng)求BeautifulSoup負(fù)責(zé)解析HTML兩者配合非常輕。對(duì)于新手來(lái)說(shuō)這套組合的學(xué)習(xí)曲線也最平緩代碼量小哪里出錯(cuò)看哪里。最關(guān)鍵的是這類(lèi)教育類(lèi)查詢(xún)網(wǎng)站結(jié)構(gòu)相對(duì)穩(wěn)定不會(huì)頻繁調(diào)整頁(yè)面不需要Scrapy那種強(qiáng)大的健壯性設(shè)計(jì)。另外我特意沒(méi)有用Selenium。當(dāng)時(shí)有同事建議用瀏覽器自動(dòng)化模擬點(diǎn)擊因?yàn)橛X(jué)得頁(yè)面是動(dòng)態(tài)的。但實(shí)際看Network面板之后發(fā)現(xiàn)數(shù)據(jù)內(nèi)容就在HTML里沒(méi)必要啟動(dòng)一個(gè)瀏覽器進(jìn)程去渲染那樣反而慢還要處理等待時(shí)間、瀏覽器版本兼容的問(wèn)題。能不用瀏覽器就不用瀏覽器這是小項(xiàng)目提效的第一原則。2.2 環(huán)境準(zhǔn)備與數(shù)據(jù)字段設(shè)計(jì)本機(jī)環(huán)境是Python 3.8Windows系統(tǒng)用的庫(kù)只有requests和beautifulsoup4。安裝命令很簡(jiǎn)單pip install requests beautifulsoup4如果你想在原有環(huán)境里不污染全局可以先建一個(gè)虛擬環(huán)境python -m venv venv venv\Scripts\activate pip install requests beautifulsoup4字段設(shè)計(jì)這塊我一開(kāi)始就在代碼里寫(xiě)了一個(gè)字段列表后面解析和寫(xiě)入都用它做標(biāo)準(zhǔn)。這樣即使網(wǎng)站顯示順序變了只要調(diào)整解析邏輯最終輸出的一致性不會(huì)亂。字段名類(lèi)型說(shuō)明yearstr招生年份固定為2020school_codestr院校代碼原始文本保留school_namestr院校名稱(chēng)major_namestr專(zhuān)業(yè)類(lèi)名稱(chēng)subject_requirementstr選考科目要求remarkstr備注可能為空字段類(lèi)型統(tǒng)一用字符串是刻意的。后續(xù)如果要做篩選和統(tǒng)計(jì)院校代碼、院校名稱(chēng)這些本質(zhì)上就是字符串雖然年份看著像數(shù)字但保存成文本可以避免導(dǎo)入Excel時(shí)開(kāi)頭0被吞掉的問(wèn)題。備注字段可能存在缺失寫(xiě)入CSV時(shí)留空即可。3. 爬蟲(chóng)核心實(shí)現(xiàn)請(qǐng)求、解析、落庫(kù)全流程3.1 請(qǐng)求構(gòu)造與分頁(yè)規(guī)則確認(rèn)第一步是確認(rèn)查詢(xún)地址和參數(shù)格式。假設(shè)查詢(xún)頁(yè)面的地址是https://example.com/xkkm/query頁(yè)面默認(rèn)每頁(yè)顯示20條記錄翻頁(yè)時(shí)URL變成了https://example.com/xkkm/query?page2pageSize20這說(shuō)明是一個(gè)標(biāo)準(zhǔn)的GET分頁(yè)查詢(xún)。我直接用requests的params參數(shù)來(lái)組織請(qǐng)求參數(shù)會(huì)自動(dòng)拼接到URL后面比手寫(xiě)字符串方便也避免中文編碼出錯(cuò)。import requests from bs4 import BeautifulSoup import csv import time import random BASE_URL https://example.com/xkkm/query HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36, Accept: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8, Referer: https://example.com/ } def fetch_page(page, page_size20): params { page: page, pageSize: page_size } resp requests.get(BASE_URL, paramsparams, headersHEADERS, timeout10) resp.encoding utf-8 resp.raise_for_status() return resp.text這里有幾個(gè)細(xì)節(jié)值得注意。第一是resp.encoding查詢(xún)網(wǎng)站返回的是UTF-8編碼但如果不顯式設(shè)置requests有時(shí)會(huì)根據(jù)響應(yīng)頭里的charset來(lái)推斷偶爾會(huì)推斷錯(cuò)誤就會(huì)導(dǎo)致中文全部亂碼。第二是timeout時(shí)間的設(shè)置必須寫(xiě)否則網(wǎng)絡(luò)異常時(shí)請(qǐng)求會(huì)一直卡住拖垮整個(gè)采集流程。第三是User-Agent默認(rèn)的python-requests標(biāo)識(shí)容易被服務(wù)端攔截偽裝成瀏覽器是基本禮儀。分頁(yè)總頁(yè)數(shù)怎么確定我在實(shí)際解析中發(fā)現(xiàn)頁(yè)面底部有一個(gè)分頁(yè)欄里面顯示“共XX頁(yè)”。先請(qǐng)求第一頁(yè)然后從HTML里提取總頁(yè)數(shù)再做循環(huán)。提取方式可以靈活處理一般是找一個(gè)class為pagination或者page-info的節(jié)點(diǎn)然后用正則或者字符串截取數(shù)字。def get_total_pages(html): soup BeautifulSoup(html, html.parser) page_info soup.select_one(.page-info) if not page_info: return 1 # 比如文本是“共 123 頁(yè)”提取數(shù)字 import re match re.search(r共\s*(\d)\s*頁(yè), page_info.get_text()) return int(match.group(1)) if match else 13.2 表格解析與字段清洗拿到HTML文本后先用BeautifulSoup解析然后定位到數(shù)據(jù)表格。這個(gè)網(wǎng)站的表格結(jié)構(gòu)很標(biāo)準(zhǔn)外層是table標(biāo)簽表頭是thead數(shù)據(jù)行都在tbody里。每一行有六個(gè)單元格分別對(duì)應(yīng)院校代碼、院校名稱(chēng)、專(zhuān)業(yè)類(lèi)名稱(chēng)、選考科目要求、備注和其他信息。解析代碼寫(xiě)起來(lái)不算難真正的坑在于單元格內(nèi)部的隱藏結(jié)構(gòu)。有的單元格里不只是純文本可能嵌套了span、a標(biāo)簽甚至還有換行。直接用get_text()拿到的是所有子標(biāo)簽文本的拼接必須用stripTrue去除首尾空白同時(shí)把內(nèi)部的換行和多余空格替換掉。def parse_table(html): soup BeautifulSoup(html, html.parser) rows soup.select(table tbody tr) data [] for row in rows: cells row.find_all(td) if len(cells) 6: continue school_code cells[0].get_text(stripTrue) school_name cells[1].get_text(stripTrue) major_name cells[2].get_text(stripTrue) subject_req cells[3].get_text(stripTrue) remark cells[4].get_text(stripTrue) # 如果單元格內(nèi)有多個(gè)換行統(tǒng)一替換成空格 import re subject_req re.sub(r\s, , subject_req) major_name re.sub(r\s, , major_name) data.append({ year: 2020, school_code: school_code, school_name: school_name, major_name: major_name, subject_requirement: subject_req, remark: remark }) return data為什么用CSS選擇器table tbody tr而不是find_all(tr)因?yàn)槿绻砀窭镞€有嵌套表格find_all(tr)會(huì)把內(nèi)層行也找出來(lái)造成解析錯(cuò)亂。用tbody限定最外層數(shù)據(jù)體干凈利落。另外解析時(shí)一定不要把表頭行混進(jìn)去。表頭通常是thead中的tr用table tbody tr選擇器就已經(jīng)天然過(guò)濾了表頭。但如果網(wǎng)站結(jié)構(gòu)不規(guī)范需要額外判斷第一個(gè)單元格的內(nèi)容是否為“院校代碼”之類(lèi)的文本做好二次過(guò)濾。3.3 數(shù)據(jù)落地CSV寫(xiě)入與去重?cái)?shù)據(jù)最終要交到別人手里Excel是最通用的格式。我選擇用CSV存儲(chǔ)然后用一個(gè)特殊編碼處理中文問(wèn)題。Python自帶的csv模塊寫(xiě)入中文時(shí)如果用默認(rèn)編碼Excel打開(kāi)會(huì)亂碼。解決方案是寫(xiě)入時(shí)使用utf-8-sig編碼它會(huì)自動(dòng)添加BOM頭Excel識(shí)別就沒(méi)問(wèn)題。def save_to_csv(all_data, output_filexuangao_2020.csv): with open(output_file, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnames[ year, school_code, school_name, major_name, subject_requirement, remark ]) writer.writeheader() writer.writerows(all_data)去重邏輯是在循環(huán)里維護(hù)一個(gè)已見(jiàn)集合用院校代碼加專(zhuān)業(yè)類(lèi)名稱(chēng)加選考科目要求拼接作為鍵。理論上同一個(gè)院校的同一個(gè)專(zhuān)業(yè)只出現(xiàn)一次但查詢(xún)系統(tǒng)偶爾會(huì)有重復(fù)展示。如果不去重后續(xù)做統(tǒng)計(jì)分析時(shí)計(jì)數(shù)就會(huì)偏高。def collect_all_pages(total_pages): all_data [] seen set() for page in range(1, total_pages 1): html fetch_page(page) page_data parse_table(html) for item in page_data: key (item[school_code], item[major_name], item[subject_requirement]) if key not in seen: seen.add(key) all_data.append(item) print(f第 {page} 頁(yè)完成累計(jì) {len(all_data)} 條) time.sleep(random.uniform(0.5, 1.5)) return all_data這里每抓完一頁(yè)強(qiáng)制休息0.5到1.5秒。別小看這個(gè)sleep它一方面控制請(qǐng)求頻率避免給服務(wù)器造成壓力另一方面也能降低被臨時(shí)限流的概率。later如果你要抓更頻繁可以把間隔壓縮到0.3秒但我不建議。3.4 完整的采集循環(huán)把上面幾個(gè)函數(shù)串起來(lái)主流程就是先獲取總頁(yè)數(shù)然后循環(huán)抓取、解析、去重、保存。直接運(yùn)行這個(gè)腳本就能在當(dāng)前目錄生成xuangao_2020.csv。def main(): first_html fetch_page(1) total_pages get_total_pages(first_html) print(f檢測(cè)到總頁(yè)數(shù){total_pages}) all_data collect_all_pages(total_pages) save_to_csv(all_data) print(f采集完成共 {len(all_data)} 條記錄) if __name__ __main__: main()在實(shí)際跑的時(shí)候我記得總共跑了不到兩百個(gè)頁(yè)面最后落地的有效記錄大概幾千條全程耗時(shí)大概十幾分鐘。這個(gè)速度完全夠用而且不會(huì)對(duì)目標(biāo)網(wǎng)站造成明顯影響。如果數(shù)據(jù)量比這個(gè)大一個(gè)量級(jí)再考慮并發(fā)優(yōu)化也不遲。4. 踩坑實(shí)錄翻頁(yè)丟失、亂碼與數(shù)據(jù)重復(fù)怎么處理4.1 翻頁(yè)后數(shù)據(jù)沒(méi)有變化是怎么回事這是我第一次跑腳本時(shí)遇到的最詭異的問(wèn)題。第一頁(yè)正常第二頁(yè)返回的內(nèi)容和第一頁(yè)一模一樣。當(dāng)時(shí)我沒(méi)有看URL而是直接用了response的URL做檢查發(fā)現(xiàn)requests的params參數(shù)確實(shí)變了但返回內(nèi)容還是第一頁(yè)。后來(lái)才意識(shí)到這種查詢(xún)系統(tǒng)對(duì)分頁(yè)參數(shù)的處理依賴(lài)某個(gè)隱藏字段比如頁(yè)面里的requestId或者sessionId首次請(qǐng)求時(shí)服務(wù)器分配了一個(gè)會(huì)話標(biāo)識(shí)翻頁(yè)時(shí)必須帶上這個(gè)標(biāo)識(shí)才能繼續(xù)取數(shù)。解決辦法很簡(jiǎn)單先用requests.Session()創(chuàng)建一個(gè)會(huì)話對(duì)象讓cookie自動(dòng)管理。session requests.Session() def fetch_page_with_session(page, page_size20): params {page: page, pageSize: page_size} resp session.get(BASE_URL, paramsparams, headersHEADERS, timeout10) resp.encoding utf-8 resp.raise_for_status() return resp.text改成Session之后翻頁(yè)正常了。這種坑在表單類(lèi)網(wǎng)站里非常常見(jiàn)尤其是登錄后查詢(xún)、分頁(yè)顯示的系統(tǒng)。以后遇到翻頁(yè)異常先檢查是不是cookie或者會(huì)話狀態(tài)沒(méi)維持住。4.2 中文亂碼與字段內(nèi)容碎片化亂碼問(wèn)題多半是編碼判斷錯(cuò)誤。第一版代碼沒(méi)寫(xiě)resp.encoding結(jié)果抓回來(lái)的院校名稱(chēng)變成一堆“錕斤拷”。requests的推測(cè)機(jī)制并不總是準(zhǔn)確尤其在響應(yīng)頭缺失charset時(shí)。顯式指定編碼是最保險(xiǎn)的。字段碎片化則是單元格內(nèi)存在不規(guī)整結(jié)構(gòu)造成的。比如有的院校名稱(chēng)很長(zhǎng)頁(yè)面里用了換行展示get_text(stripTrue)之后變成了名字中間帶空格。還有的選考科目要求是“物理、化學(xué)”并分成兩行直接拼接就變成“物理化學(xué)”中間沒(méi)有分隔符。我用re.sub(r\s, , text)把所有連續(xù)空白字符統(tǒng)一替換成單個(gè)空格再手動(dòng)把逗號(hào)、頓號(hào)規(guī)范一下。這類(lèi)清洗邏輯不復(fù)雜但一定要在爬取階段做掉否則數(shù)據(jù)進(jìn)了CSV再處理會(huì)非常被動(dòng)。4.3 請(qǐng)求失敗與中斷恢復(fù)跑長(zhǎng)任務(wù)最怕中途掛掉。網(wǎng)絡(luò)抖動(dòng)、服務(wù)器超時(shí)、偶發(fā)的狀態(tài)碼異常都可能導(dǎo)致腳本中斷。我在代碼里加了簡(jiǎn)單的重試機(jī)制遇到請(qǐng)求異常就等待一段時(shí)間后再試最多重試三次。def fetch_with_retry(page, retries3): for attempt in range(retries): try: return fetch_page_with_session(page) except requests.RequestException as e: print(f第 {page} 頁(yè)請(qǐng)求失敗第 {attempt 1} 次重試{e}) time.sleep(2 ** attempt) return None但重試只能解決臨時(shí)故障如果程序中斷后重新再跑還是會(huì)從頭開(kāi)始。更穩(wěn)妥的做法是記錄已經(jīng)完成的分頁(yè)。我用一個(gè)簡(jiǎn)單的斷點(diǎn)文件每頁(yè)成功解析后就寫(xiě)入當(dāng)前頁(yè)數(shù)下次啟動(dòng)時(shí)從斷點(diǎn)頁(yè)繼續(xù)。這樣即使跑了半小時(shí)斷掉也不需要重頭再來(lái)。def load_last_page(): try: with open(checkpoint.txt, r) as f: return int(f.read().strip()) except (FileNotFoundError, ValueError): return 1 def save_checkpoint(page): with open(checkpoint.txt, w) as f: f.write(str(page))這種斷點(diǎn)方式比數(shù)據(jù)庫(kù)快照輕量得多對(duì)于小規(guī)模采集已經(jīng)足夠。如果是更正式的工程建議把采集進(jìn)度寫(xiě)到SQLite或單獨(dú)的進(jìn)度表里。4.4 常見(jiàn)問(wèn)題速查表我把這次實(shí)戰(zhàn)里最典型的幾個(gè)問(wèn)題整理成一張表后面遇到類(lèi)似情況可以直接對(duì)照?,F(xiàn)象可能原因解決辦法中文亂碼響應(yīng)編碼推斷錯(cuò)誤顯式設(shè)置resp.encoding utf-8第二頁(yè)內(nèi)容和第一頁(yè)一樣分頁(yè)依賴(lài)會(huì)話Cookie改用requests.Session()部分?jǐn)?shù)據(jù)缺失表格結(jié)構(gòu)不規(guī)整tr嵌套用table tbody tr限定范圍字段間有多余空格或換行HTML源碼內(nèi)部有標(biāo)簽嵌套get_text(stripTrue)后再用正則清洗跑到一半被限流請(qǐng)求過(guò)于頻繁增加隨機(jī)延時(shí)sleep數(shù)據(jù)重復(fù)寫(xiě)入未做唯一性校驗(yàn)維護(hù)已見(jiàn)集合按唯一鍵去重程序中斷后從頭再跑缺少斷點(diǎn)記錄寫(xiě)頁(yè)面斷點(diǎn)文件5. 數(shù)據(jù)到手之后統(tǒng)計(jì)與擴(kuò)展玩法5.1 用CSV數(shù)據(jù)做選考要求分布統(tǒng)計(jì)爬蟲(chóng)跑完手里攢下幾千條高校專(zhuān)業(yè)記錄單純交給需求方還不夠。我用pandas做了個(gè)快速統(tǒng)計(jì)看看選考科目要求大致是什么分布哪些專(zhuān)業(yè)幾乎都要求物理哪些專(zhuān)業(yè)完全不限。import pandas as pd df pd.read_csv(xuangao_2020.csv, encodingutf-8-sig) # 統(tǒng)計(jì)選考科目要求的分布 req_counts df[subject_requirement].value_counts() print(req_counts.head(20))統(tǒng)計(jì)結(jié)果里占比最高的通常是“物理必選”“不限”和“化學(xué)或生物任選一門(mén)”這幾類(lèi)。物理必選的專(zhuān)業(yè)大多是工科、計(jì)算機(jī)、電子信息相關(guān)不限選科的多是文科、藝術(shù)類(lèi)、部分管理類(lèi)專(zhuān)業(yè)。這個(gè)分布對(duì)考生選科策略很有參考意義也方便招生部門(mén)做數(shù)據(jù)復(fù)盤(pán)。如果想把數(shù)據(jù)變成一張方便篩選的表可以直接用Excel打開(kāi)CSV選中年份列和選考科目要求列加篩選按鈕。之后按“不限選科”“物理必選”等關(guān)鍵詞逐項(xiàng)篩選效率比在網(wǎng)頁(yè)上翻頁(yè)高得多。5.2 這個(gè)爬蟲(chóng)還能遷移到哪些場(chǎng)景這次任務(wù)的思路不局限于高考選考數(shù)據(jù)。凡是“列表頁(yè)分頁(yè)表格HTML”結(jié)構(gòu)的網(wǎng)站都可以用同一套代碼邏輯快速改造。比如高校招生計(jì)劃查詢(xún)、職業(yè)資格目錄查詢(xún)、企業(yè)資質(zhì)公示列表結(jié)構(gòu)上都大同小異。遷移時(shí)主要改三個(gè)地方查詢(xún)地址BASE_URL、表格行的CSS選擇器、字段清單fieldnames。其他像請(qǐng)求頭、會(huì)話保持、翻頁(yè)循環(huán)、去重寫(xiě)入這些邏輯基本可以原封不動(dòng)復(fù)用。甚至可以把腳本封裝成一個(gè)通用函數(shù)傳入不同的URL、選擇器和字段名就變成了一個(gè)小型通用列表采集器。如果目標(biāo)網(wǎng)站的數(shù)據(jù)不是HTML表格而是JSON接口就把parse_table換成json解析整體架構(gòu)不需要變。requests拿到的還是同樣的響應(yīng)只是解析方式不同。掌握這一個(gè)套路遇到類(lèi)似場(chǎng)景基本能直接套模板。最后再分享一個(gè)小技巧這類(lèi)數(shù)據(jù)爬完以后最好在文件里額外增加一列采集時(shí)間記錄數(shù)據(jù)快照的抓取時(shí)間。以后數(shù)據(jù)源更新了你能清楚分辨哪些是舊數(shù)據(jù)、哪些是新數(shù)據(jù)也方便做對(duì)比。我當(dāng)時(shí)給CSV加了采集日期列后來(lái)再做年度對(duì)比時(shí)直接按年份和采集日期兩個(gè)維度篩選非常有用。