乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      一個超詳細(xì)的 Python 入門爬蟲實戰(zhàn)案例

       瑾月8fzemmqejz 2019-12-19

      本文轉(zhuǎn)自公眾號:超哥的雜貨鋪

      寫在前面:本文從北京公交路線數(shù)據(jù)的獲取和預(yù)處理入手,記錄使用python中requests庫獲取數(shù)據(jù),pandas庫預(yù)處理數(shù)據(jù)的過程。文章在保證按照一定處理邏輯的前提下,以自問自答的方式,對其中每一個環(huán)節(jié)進(jìn)行詳細(xì)闡述。本次代碼均在jupyter notebook中測試通過,希望對大家有所啟示。

      數(shù)據(jù)獲取:

      本次我們從公交網(wǎng)獲取北京公交的數(shù)據(jù)。

      (http://beijing./lines_all.html)

      如上圖所示,數(shù)據(jù)獲取分為請求,解析存儲三個最主要的步驟。

      1.如何用python模擬網(wǎng)絡(luò)請求?

      使用request庫可以模擬不同的請求,例如requests.get()模擬get請求,requests.post()模擬post請求。必要的時候可以添加請求頭header,header通常包括user-agent,cookie,refer等信息,還可以增加請求參數(shù)data和代理信息。主要代碼形式為:response = requests.request('GET', url, headers=headers, params=querystring)response是網(wǎng)站返回的響應(yīng)信息,可以調(diào)用其text方法獲取網(wǎng)站的HTML源碼。本次我們的目標(biāo)網(wǎng)站比較簡單,獲取網(wǎng)頁源碼的代碼如下:

      1url = 'http://beijing./lines_all.html'
      2text = requests.get(url).text

      2.如何對網(wǎng)頁進(jìn)行解析?

      python中提供了多種庫用于網(wǎng)頁解析,例如lxml,BeautifulSoup,pyquery等。每一個工具都有相應(yīng)的解析規(guī)則,但都是把HTML文檔當(dāng)做一個DOM樹,通過選擇器進(jìn)行節(jié)點和屬性的定位。本次我們使用lxml對網(wǎng)頁進(jìn)行解析,主要用到了xpath的語法。lxml的執(zhí)行效率通常也比BeautifulSoup更高一些。

      1doc = etree.HTML(text)
      2all_lines = doc.xpath('//div[@class='list']/ul/li')
      3for line in all_lines:
      4    line_name = line.xpath('./a/text()')[0].strip()
      5    line_url = line.xpath('./a/@href')[0]

      我們將圖和代碼結(jié)合起來看。第一行代碼將上一步返回的HTML文本轉(zhuǎn)換為xpath可以解析的對象。第二行代碼定位到class=list的div下面所有的li標(biāo)簽,即右圖中的紅色框的部分,得到的是一個列表。從第三行開始對其進(jìn)行遍歷,處理每一個li下面的a標(biāo)簽。第4行取出a標(biāo)簽下的文本,用到了xpath的text()方法,對應(yīng)到第一個li就是“北京1路公交車路線”,第5行取出a標(biāo)簽下對應(yīng)的鏈接,用到了xpath的@href取出a標(biāo)簽下的href屬性值。直接取都是列表的形式,所以需要用索引取出具體的值。

      這樣我們就可以得到整個公交線路列表中的線路名稱和線路url。然后從線路url出發(fā),就可以獲取每條線路的具體信息。如下面代碼和圖片所示,雖然數(shù)據(jù)略多,但主要的邏輯和上面類似,可以查看代碼中的注釋。

      注:左右滑動查看詳細(xì)代碼

      1url = 'http://beijing./xianlu_38753'#先以一個url為例,進(jìn)行頁面的分析
      2text = requests.get(url).text
      3print(len(text))
      4doc = etree.HTML(text)
      5infos = doc.xpath('//div[@class='gj01_line_header clearfix']')#定位到相應(yīng)的div塊
      6for info in infos:
      7    start_stop = info.xpath('./dl/dt/a/text()')#獲取起點站和終點站的文本,xpath的邏輯為:div->dl->dt->a
      8    op_times = info.xpath('./dl/dd[1]/b/text()')#獲取運營時間的文本,xpath的邏輯為:div->dl->第一個dd->b
      9    interval = info.xpath('./dl/dd[2]/text()')#獲取發(fā)車間隔的文本,xpath的邏輯為:div->dl->第二個dd
      10    price = info.xpath('./dl/dd[3]/text()')#獲取票價信息的文本,xpath的邏輯為:div->dl->第三個dd
      11    company = info.xpath('./dl/dd[4]/text()')#獲取汽車公司的文本,xpath的邏輯為:div->dl->第四個dd
      12    up_times = info.xpath('./dl/dd[5]/text()')#獲取更新時間的文本,xpath的邏輯為:div->dl->第五個dd
      13    all_stations_up = doc.xpath('//ul[@class='gj01_line_img JS-up clearfix']')#定位到相應(yīng)的div塊
      14    for station in all_stations_up:
      15        station_name = station.xpath('./li/a/text()')#遍歷取出該條線路上的站點名稱
      16    all_stations_down = doc.xpath('//ul[@class='gj01_line_img JS-down clearfix']')#定位到返程線路相應(yīng)的div塊
      17    for station in all_stations_down:
      18        station_name = station.xpath('./li/a/text()')#遍歷取出該條線路上返程的站點名稱
      19如果將獲取的文本都輸出(請自行添加相應(yīng)的print語句)運行結(jié)果如下:
      20['老山公交場站(1)''四惠樞紐站(27)']
      21['5:00-23:00']
      22['5:00-23:00']
      23['發(fā)車間隔:未知']
      24['票價信息:10公里以內(nèi)票價2元,每增加5公里以內(nèi)加價1元,最高票價6元']
      25['汽車公司:北京公交集團(tuán)第六客運分公司']
      26['更新時間:2015-04-05 03:32:16']
      27['老山公交場站(1)''老山南路東口(2)''地鐵八寶山站(3)''玉泉路口西(4)''五棵松橋西(6)''翠微路口(8)''公主墳(9)''軍事博物館(10)''木樨地西(11)''工會大樓(12)''南禮士路(13)''復(fù)興門內(nèi)(13)''西單路口東(15)''天安門西(16)''天安門東(17)''東單路口西(18)''北京站口東(19)''日壇路(20)''永安里路口西(21)''大北窯西(22)''大北窯東(23)''郎家園(23)''四惠樞紐站(27)']
      28['四惠樞紐站(27)''八王墳西(24)''郎家園(23)''大北窯東(23)''大北窯西(22)''永安里路口西(21)''日壇路(20)''北京站口東(19)''東單路口西(18)''天安門東(17)''天安門西(16)''西單路口東(15)''復(fù)興門內(nèi)(13)''南禮士路(13)''工會大樓(12)''木樨地西(11)''軍事博物館(10)''公主墳(9)''翠微路口(8)''五棵松橋東(6)''玉泉路口西(4)''地鐵八寶山站(3)''老山南路東口(2)''老山公交場站(1)']

      3.如何存儲獲取的數(shù)據(jù)?

      數(shù)據(jù)存儲的載體通常有文件(例如csv,excel)和數(shù)據(jù)庫(例如mysql,MongoDB)。我們這里選擇了csv文件的形式,一方面是數(shù)據(jù)量不是太大,另一方面也不需要進(jìn)行數(shù)據(jù)庫安裝,只需將數(shù)據(jù)整理成dataframe的格式,直接調(diào)用pandas的to_csv方法就可以將dataframe寫入csv文件中。主要代碼如下:

      注:左右滑動查看詳細(xì)代碼

       1#準(zhǔn)備一個存儲數(shù)據(jù)的字典
      2df_dict = {
      3    'line_name': [], 'line_url': [], 'line_start': [], 'line_stop': [],
      4    'line_op_time': [], 'line_interval': [], 'line_price': [], 'line_company': [],
      5    'line_up_times': [], 'line_station_up': [], 'line_station_up_len': [],
      6    'line_station_down': [], 'line_station_down_len': [] 
      7}
      8#將上面獲取的數(shù)據(jù)寫入到字典中,注意這里只是示例,實際運行時候要將下面的代碼放到循環(huán)中,每解析一條線路就需要append一次。
      9df_dict['line_name'].append(line_name)
      10df_dict['line_url'].append(line_url)
      11df_dict['line_start'].append(start_stop[0])
      12df_dict['line_stop'].append(start_stop[1])
      13df_dict['line_op_time'].append(op_times[0])
      14df_dict['line_interval'].append(interval[0][5:])#為了把前面的文字“發(fā)車間隔”截掉,其余的類似
      15df_dict['line_company'].append(company[0][5:])
      16df_dict['line_price'].append(price[0][5:])
      17df_dict['line_up_times'].append(up_times[0][5:])
      18df_dict['line_station_up'].append(station_up_name)
      19df_dict['line_station_up_len'].append(len(station_up_name))
      20df_dict['line_station_down'].append(station_down_name)
      21df_dict['line_station_down_len'].append(len(station_down_name))
      22#將數(shù)據(jù)保存成csv文件
      23df = pd.DataFrame(df_dict)
      24df.to_csv('bjgj_lines_utf8.csv', encoding='utf-8', index=None)

      4.看一看完整代碼?

      以上我們分模擬請求,網(wǎng)頁解析,數(shù)據(jù)存儲3個步驟,學(xué)習(xí)了數(shù)據(jù)獲取的流程。實際運行過程中,還需要增加一些保證代碼“健壯性”的邏輯。例如,控制爬取的頻率,處理請求失敗的情況,處理不同的線路網(wǎng)頁結(jié)構(gòu)可能有差異的情況等等。本次的數(shù)據(jù)源沒有做很多反扒限制,因此前兩種情況我們可以不處理。至于第三種,有的路線會出現(xiàn)線路運營時間是空值的情況,需要進(jìn)行判斷。另外還可以增加一些爬蟲運行過程的提示信息,讓我們知道爬取進(jìn)度,當(dāng)然你也可以增加多線程,代理,ua切換等代碼,此處我們還用不上這些。完整的代碼可以在后臺回復(fù)“北京公交”進(jìn)行獲取。

      數(shù)據(jù)預(yù)處理

      在上一步獲取數(shù)據(jù)之后,我們就可以使用pandas進(jìn)行數(shù)據(jù)的分析工作。在正式的分析之前,數(shù)據(jù)預(yù)處理非常重要,它保證了數(shù)據(jù)的質(zhì)量,也為后續(xù)的工作奠定了重要的基礎(chǔ)。通常數(shù)據(jù)預(yù)處理在實際工作中都會占用比較多的時間。雖然我們這里的數(shù)據(jù)已經(jīng)足夠“結(jié)構(gòu)化”,但仍然不可避免存在一些問題。下面我們就來一探究竟。

      5.如何讀取數(shù)據(jù)?

      使用pandas提供的read_csv方法,該方法有很多可選的參數(shù),例如指定索引,列名,編碼等。對于本次數(shù)據(jù),直接使用默認(rèn)的即可。讀取的ori_data是dataframe類型,調(diào)用head方法可以輸出前5行的樣例數(shù)據(jù)。

      1ori_data = pd.read_csv('bjgj_lines_utf8.csv')
      2ori_data.head()

      6.如何查看每一列數(shù)據(jù)的唯一值的個數(shù)?(如何查看有多少條線路)

      可以使用dataframe的nunique方法,該方法輸出每一列有幾個唯一的值。

       1ori_data.nunique()
      2輸出結(jié)果如下:
      3line_name                1986
      4line_url                 2002
      5line_start                989
      6line_stop                1123
      7line_op_time              560
      8line_interval               4
      9line_price                126
      10line_company               82
      11line_up_times             650
      12line_station_up          1928
      13line_station_up_len        80
      14line_station_down        1700
      15line_station_down_len      80
      16dtypeint64

      由于線路很多,我們在原始網(wǎng)頁中很難發(fā)現(xiàn)是否會有重復(fù)的線路。但從上面觀察line_name和line_url兩個字段,line_name有1986個唯一值,line_url有2002個唯一值。說明line_name存在重復(fù):會有名稱相同的線路對應(yīng)不同的line_url。所以接下來我們需要進(jìn)行重復(fù)值的剔除。

      7.如何找出重復(fù)的值?

      出現(xiàn)了線路名稱的重復(fù),但卻有不同的line_url,究竟是確實是線路“重名”還是線路“重復(fù)”?我們需要看一下數(shù)據(jù)重復(fù)的具體情況。因此需要把重復(fù)的行都找出來看看??梢允褂胮andas的duplicated方法,它可以對dataframe的指定列查看是否重復(fù),返回True和False,代碼如下。

      1d = ori_data.duplicated(subset=['line_name'])
      2dup_data = ori_data[d]
      3dup_data

      這是所有重復(fù)出現(xiàn)過的line_name值,但并不是所有重復(fù)的值(例如22路重復(fù)出現(xiàn)過,但22路在結(jié)果中只有一條,不便于觀察除了名字之外是否還有其他字段的重復(fù))。為了找出所有重復(fù)的值(例如輸出所有22路的記錄),我們可以從原數(shù)據(jù)中取line_name是這些值的所有行,代碼和思路如下:

      1#首先定義一個列表,每找出一行l(wèi)ine_name在上面范圍內(nèi)的,
      2#就將這行加入列表,然后調(diào)用concat方法將列表拼接成#dataframe
      3dup_lines = []
      4for name in dup_data.line_name:
      5    tmp_lines = ori_data[ori_data['line_name'] == name]
      6    dup_lines.append(tmp_lines)
      7    dup_data_all = pd.concat(dup_lines)
      8dup_data_all

      觀察dup_data_all,確實同一個線路名字存在重復(fù)的記錄,而且其余信息也是幾乎都相同的,這確認(rèn)了我們認(rèn)為的線路”重名“現(xiàn)象是不存在的。但同一條線路的信息具體以哪一個為準(zhǔn)呢?注意到有更新時間line_up_time字段,因此我們可以以最新時間的信息為準(zhǔn)。

      8.如何對原數(shù)據(jù)剔除重復(fù)值?

      這里考慮兩種思路。第一種,直接對原數(shù)據(jù)進(jìn)行操作,當(dāng)line_name存在重復(fù)時,保留最近更新時間的記錄。第二種,將原數(shù)據(jù)中的dup_data_all部分完全刪除,拼接上dup_data_all去除重復(fù)的部分。兩種思路都需要刪除line_name重復(fù)的記錄,保留一個時間最新的。pandas本身有drop_duplicates方法,使用keep=last或keep=first參數(shù)就可以指定保留的記錄。但在這之前我們需要將line_up_time轉(zhuǎn)換為pandas可以識別的時間類型,然后對其進(jìn)行排序。下面來看代碼:

      注:左右滑動查看詳細(xì)代碼

      1#方法1
      2ori_data['line_up_times'] = pd.to_datetime(ori_data['line_up_times'], format='%Y-%m-%d %H:%M:%S')#使用to_datetime方法,指定format,將字符串轉(zhuǎn)換為pandas的時間類型。
      3ori_data.sort_values(by=['line_name''line_up_times'], ascending=[TrueTrue], inplace=True)#使用sort_values方法,對line_name和line_up_time排序
      4drop_dup_line1 = ori_data.drop_duplicates(subset=['line_name'], keep='last')#由于是升序排列,所以keep=last就可以保留最新事件的記錄
      5len(drop_dup_line1)#結(jié)果是1986 
      6
      7方法2
      8dup_data_all['line_up_times'] = pd.to_datetime(dup_data_all['line_up_times'], format='%Y-%m-%d %H:%M:%S')#使用to_datetime方法,指定format,將字符串轉(zhuǎn)換為pandas的時間類型。
      9dup_data_all.sort_values(by=['line_name''line_up_times'], ascending=[TrueTrue], inplace=True)#使用sort_values方法,對line_name和line_up_time排序
      10dup_data_all.drop_duplicates(subset=['line_name'], keep='last', inplace=True)#使用keep=last保留時間更新的記錄
      11
      12other_data = ori_data[~ori_data['line_name'].isin(dup_data_all.line_name)]#獲取原數(shù)據(jù)中剔除了重復(fù)線路的數(shù)據(jù):取名字不在dup_data_all的line_name集合中的記錄
      13drop_dup_line2 = pd.concat([other_data, dup_data_all]) #拼接兩部分?jǐn)?shù)據(jù)
      14len(drop_dup_line2)#結(jié)果是1986 

      如何比較兩種方法獲得的結(jié)果線路是否一致?我們可以用下面的代碼進(jìn)行。

      1drop_dup_line2.sort_values(by=['line_name''line_up_times'], ascending=[TrueTrue], inplace=True)#由于drop_dup_line1排序過,我們也對drop_dup_line2進(jìn)行相同規(guī)則的排序
      2res = drop_dup_line1['line_name'].values.ravel() == drop_dup_line2['line_name'].values.ravel()#ravel()方法將數(shù)組展開,res是一個布爾值組成的ndarray數(shù)組,結(jié)果為true表示對應(yīng)元素相等
      3res = [1 for i in res.flat if i]  
      4sum(res)#使用flat方法可以對ndarray進(jìn)行遍歷,sum看一下一共有多少個true,結(jié)果是1986,說明drop_dup_line1和drop_dup_line2對應(yīng)每一個位置的元素都相同

      這樣對于重復(fù)數(shù)據(jù)的處理就結(jié)束了,我們使用drop_dup_line1來進(jìn)行下面的分析。

      9.如何刪除地鐵線路?

      雖然我們爬取的是公交路線,但程序運行過程中我也發(fā)現(xiàn)了地鐵的線路(其實地鐵也是廣義上的公交啦)。如果我們的目的是對純粹的公交線路進(jìn)行分析,就需要將地鐵的線路刪除。直觀的思路是剔除線路名稱中含有“地鐵”的記錄。

      1is_subway = drop_dup_line1.line_name.str.contains('地鐵')#使用.str將其轉(zhuǎn)換為字符串就可以使用字符串的contains方法。
      2subway_data = drop_dup_line1[is_subway]
      3subway_data

      從上圖左側(cè)可以看到subway_data的結(jié)果不僅僅有地鐵,還有一些地鐵有關(guān)的通勤線路,其實是公交。因此不能直接刪除line_name中含有“地鐵”的記錄,我們使用line_conpany中含有“地鐵”來區(qū)分,效果更好。代碼如下所示:

      1is_subway2 = drop_dup_line1.line_company.str.contains('地鐵')
      2subway_data2 = drop_dup_line1[is_subway2]
      3subway_data2

      結(jié)果如上圖右側(cè)所示,雖然最后一條也有一條“公交車路線”,但觀察整條記錄就會發(fā)現(xiàn)它其實是特殊的機場線地鐵。

      到這里,你會不會想到根據(jù)線路名稱中是否含有“公交車路線”將地鐵線路剔除?我們可以試一試。但其實上面的圖已經(jīng)告訴了我們答案:有的公交線路是“接駁線”,并不含有“公交車路線”。

      10.獲取刪除地鐵數(shù)據(jù)之后的全部數(shù)據(jù)

      在drop_dup_line1的基礎(chǔ)上,篩選出線路名稱不在subway_data2中的線路名稱的記錄即可:

      1clean_data = drop_dup_line1[~drop_dup_line1['line_name'].isin(subway_data2.line_name)]
      2len(clean_data) #結(jié)果是1963,也就是北京的公交車一共有1963條線路
      3
      4clean_data3 = drop_dup_line1[drop_dup_line1.line_name.str.contains('公交車路線')]
      5len(clean_data3) #通過是否含有“公交車線路”進(jìn)行篩選,結(jié)果是1955,應(yīng)該就是少了那些“接駁線”

      如何比較clean_data和clean_data3。這個問題其實是如何求兩個dataframe差集的問題,我們轉(zhuǎn)化為求列表的差集,代碼和結(jié)果如下所示。

      1list(set(clean_data.line_name.values).difference(set(clean_data3.line_name.values))) #找出在clean_data的line_name中但是不在clean_data3的line_name中的數(shù)據(jù)
      2list(set(clean_data3.line_name.values).difference(set(clean_data.line_name.values))) #找出在clean_data3的line_name中但是不在clean_data的line_name中的數(shù)據(jù)

      至此我們將重復(fù)數(shù)據(jù)進(jìn)行了刪除,并剔除了“地鐵”線路。但其實我們的數(shù)據(jù)預(yù)處理工作還沒有結(jié)束,我們還沒有觀察數(shù)據(jù)中是否含有缺失值。

      11.如何查看數(shù)據(jù)集中的缺失值情況?

      可以使用isnull().sum()方法查看。發(fā)現(xiàn)票價有230個缺失值。參見后面的圖片。對于缺失值我們需要在預(yù)處理階段對其進(jìn)行填充。考慮到票價數(shù)據(jù)本身不是純粹的價格數(shù)據(jù),而是一大串的文字描述,并且在公交的這種場景下,其實不同線路的票價差別不是很大,因此我們可以使用眾數(shù)對缺失值進(jìn)行填充。使用mode方法查看眾數(shù),使用fillna方法填補缺失值。

      1#查看眾數(shù)的方法:
      2clean_data.line_price.mode()#使用mode()方法查看line_price的眾數(shù)
      3clean_data.line_price.value_counts()#使用value_counts()方法查看每一個取值出現(xiàn)的次數(shù),第一個也是眾數(shù)
      4
      5clean_data.line_price.fillna(clean_data.line_price.mode()[0], inplace=True)
      6clean_data.isnull().sum()

      至此我們基本完成了重復(fù)值和缺失值的處理。

      總結(jié)

      本文我們主要借助于北京公交數(shù)據(jù)的實例,學(xué)習(xí)了使用python進(jìn)行數(shù)據(jù)獲取和數(shù)據(jù)預(yù)處理的流程。內(nèi)容雖然簡單但不失完整性。數(shù)據(jù)獲取部分主要使用requests模擬了get請求,使用lxml進(jìn)行了網(wǎng)頁解析并將數(shù)據(jù)存儲到csv文件中。數(shù)據(jù)預(yù)處理部分我們進(jìn)行了重復(fù)值和缺失值的處理,但應(yīng)該說數(shù)據(jù)預(yù)處理并沒有完成。(比如我們可以對運營時間拆分成兩列,對站點名稱進(jìn)行清理等,如何進(jìn)行預(yù)處理工作與后續(xù)的分析緊密相關(guān))。文章的重點不在于例子的難度,而在于通過具體問題學(xué)習(xí)python中數(shù)據(jù)處理的方法。所處理的問題雖然有一定的特殊性,但也方便擴展到其他場景。希望對讀到這里的你有一定的幫助。讀者可以在后臺回復(fù)“北京公交”獲取本文的數(shù)據(jù)和爬取代碼,歡迎交流學(xué)習(xí)~以清凈心看世界。

        本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多