商業資料分析從入門到入職(9)Python網路資料獲取

2020-10-05 16:00:14

前言

本文主要講Python最常見的應用之一——網路資料獲取,即爬蟲:
先介紹了網頁和網路的基礎知識,為從網頁中獲取資料打好基礎;接下來以兩個案例介紹從網路中獲取資料和處理資料的不同方式,以進一步認識Python爬蟲和資料處理。

一、網路和網頁基礎知識

1.資料來源

資料來源有很多,可以從資料庫中獲取,可以從檔案中獲取,也可以從網路中獲取,也可以直接獲取裸資料。

資料庫資料來源有很多,比如RDBMS,即關聯式資料庫管理系統,屬於結構化資料,具體包括MySQL、PostgreSQL、SQLServer、Oracle、SQLite等資料庫型別。

資料檔案包括:

  • Excel
    最常見,最有問題。

  • 分隔格式
    最常見、最受歡迎。
    包括逗號分隔符(csv)、製表符分隔符(tsv)、|分隔符等形式。
    問題包括資料欄位中的分隔、編碼等。
    如下:
    data file csv

  • 固定長度
    每一列都有固定長度。
    問題:列過大。
    如下:
    data file fixed

  • JSON
    即JavaScript Object Notation(JavaScript物件表示法)的簡寫,屬於半結構化資料。
    屬性位於冒號的左側,數值位於冒號右側,屬性用逗號分隔,多值屬性作為層次值。
    如下:
    data file json

  • XML
    Extensible Markup Language(可延伸標示語言)的簡寫,屬於半結構化資料,也是最常見的資料交換。
    如下:
    data file xml

  • Parquet
    列儲存,Spark。
    如下:
    data file parquet

網路資料:
主要為HTML,為非結構化資料。
如下:
data web html

2.網路基礎知識

網路資料傳輸,一般是先請求、再響應,中間可能經過很多次層轉發,計算機理論的OSI模型如下:
data computer web osi

每一層的過程可能如下:
data computer web layer
現在一般的網站模式為使用者端/伺服器,使用者端一般即為自己所使用的瀏覽器,伺服器是儲存網站資源、管理網路請求的平臺。

一般的請求過程如下:
(1)使用者輸入URL;
(2)使用者端傳送請求Request;
(3)伺服器接收請求Request;
(4)伺服器返回響應Response Back;
(5)使用者端接收並解析Response。

對於一個url,如https://127.0.0.1:8000/hellohttp表示協定,127.0.0.1表示主機號,8000是埠號,/hello是路徑,從而可以精確定位到要存取的資訊。

使用瀏覽器存取網站的基本操作如下:
data web browser basic

可以看到,在進行搜尋和篩選時,連結也會有所變化,以向瀏覽器請求不同的內容。

還可以使用瀏覽器的審計工具,可以檢視頁面元素網路請求樣式等。
如下:
data web browser dev

可以看到,頁面中的內容都是通過很多標籤和樣式組織起來的,這就是HTML程式碼;
同時在請求和相應的時候,都攜帶了很多引數。

進一步使用審計工具如下:
data web browser dev deep

可以看到,可以通過設定實現模擬不同的裝置進行請求,此時請求引數的User-Agent引數也隨之變化。

一個HTTP請求包括請求方法、請求路徑和HTTP版本,一個HTTP響應包括HTTP版本、狀態碼和響應體。

3.HTML、CSS和網頁資料抓取方式

網頁是由HTML程式碼組成的,資訊一般包含在這些程式碼中;
CSS是一些樣式檔案,對於獲取資料影響不大;
JavaScript程式碼可以執行一些更復雜的邏輯,對獲取資料的影響可能比較大。

一個簡單的HTML程式碼如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>首頁</h1>
    <form action="" method="post">
        <table>
            <tr>
                <td><input type="text" name="name"></td>
                <td><input type="submit" value="提交"></td>
            </tr>
        </table>
    </form>
</body>
</html>

網頁抓取一般有兩種方式:

  • 逐行掃描Line by Line
    包括簡單字串處理和正規表示式方式等。
    正規表示式是一個特殊的字元序列,它能方便檢查一個字串是否與某種模式匹配,Python中的re模組使Python擁有全部的正規表示式功能,其中,正規表示式的原理如下:
    data web re principal

    具體使用可參考https://www.runoob.com/python/python-reg-expressions.html

  • 樹形模型Tree Model
    利用HTML的樹形結構來獲取HTML中的資訊,包括BeautifulSoup、lxml等庫支援該功能。
    網路請求HTML並展示為樹形結構的過程如下:
    data web html get show

例如,對於以下案例程式碼:
data web html demo

其中,date資料為Sep 13, 2014,message資料為i didnt know that

如果使用正規表示式提取這兩個資料,方式為<h2>(.+)<\/h2><\/span>(.+)<\/li>
而使用屬性模型如BeautifulSoup提取資料,會建立如下的結構:
data web html demo tree

從而,提取資料的方式為div.h2.textdiv.ul.li.text

二、BOSS直聘資料抓取案例

1.網站預覽

以BOSS直聘https://www.zhipin.com/為例,實現較完整的網路資料抓取的過程。

網站預覽如下:
data web boss review

可以看到,在檢視器中選擇一定的HTML程式碼區域,頁面中也會有相應的高亮顯示,說明需要獲取的資料也就在這些對應的HTML程式碼中;
每一條結果的位置為class為job-list的div下面的ul下面的li下面的class為job-primary的div,有多少條職位資訊就有多少個li,其中一個li的內容如下:

<li>
    <div class="job-primary">

        <div class="info-primary">
            <div class="primary-wrapper">
                <div class="primary-box" href="/job_detail/7271f2f28169375a1nR42t-6GFpQ.html"
                    data-jid="7271f2f28169375a1nR42t-6GFpQ" data-itemid="1" data-lid="nlp-aqyTkPDQjXA.search.1"
                    data-jobid="102127880" data-index="0" ka="search_list_1" target="_blank">
                    <div class="job-title">
                        <span class="job-name"><a href="/job_detail/7271f2f28169375a1nR42t-6GFpQ.html" title="資料分析"
                                target="_blank" ka="search_list_jname_1" data-jid="7271f2f28169375a1nR42t-6GFpQ"
                                data-itemid="1" data-lid="nlp-aqyTkPDQjXA.search.1" data-jobid="102127880"
                                data-index="0">資料分析</a></span>
                        <span class="job-area-wrapper">
                            <span class="job-area">北京·朝陽區·鳥巢</span>
                        </span>
                        <span class="job-pub-time"></span>
                    </div>
                    <div class="job-limit clearfix">
                        <span class="red">50-80K·14薪</span>
                        <p>3-5年<em class="vline"></em>本科</p>
                        <div class="info-publis">
                            <h3 class="name"><img class="icon-chat"
                                    src="https://z.zhipin.com/web/geek/resource/icon-chat-v2.png">曹先生<em
                                    class="vline"></em>資料探勘</h3>
                        </div>
                        <button class="btn btn-startchat" href="javascript:;"
                            data-url="/wapi/zpgeek/friend/add.json?jobId=7271f2f28169375a1nR42t-6GFpQ&amp;lid=nlp-aqyTkPDQjXA.search.1"
                            redirect-url="/web/geek/chat?id=495f7159c0c8664a1nFz39m8EA~~">
                            <img class="icon-chat icon-chat-hover"
                                src="https://z.zhipin.com/web/geek/resource/icon-chat-hover-v2.png" alt="">
                            <span>立即溝通</span>
                        </button>
                    </div>
                    <div class="info-detail" style="top: 0px;"></div>
                </div>
            </div>
            <div class="info-company">
                <div class="company-text">
                    <h3 class="name"><a href="/gongsi/33e052361693f8371nF-3d25.html" title="京東集團招聘"
                            ka="search_list_company_1_custompage" target="_blank">京東集團</a></h3>
                    <p><a href="/i100001/" class="false-link" target="_blank"
                            ka="search_list_company_industry_1_custompage" title="電子商務行業招聘資訊">電子商務</a><em
                            class="vline"></em>已上市<em class="vline"></em>10000人以上</p>
                </div>
                <a href="/gongsi/33e052361693f8371nF-3d25.html" ka="search_list_company_1_custompage_logo"
                    target="_blank"><img class="company-logo"
                        src="https://img.bosszhipin.com/beijin/mcs/bar/20191129/3cdf5ba2149e309b38868b62ae9c22cabe1bd4a3bd2a63f070bdbdada9aad826.jpg?x-oss-process=image/resize,w_100,limit_0"
                        alt=""></a>
            </div>
        </div>
        <div class="info-append clearfix">
            <div class="tags">
                <span class="tag-item">Excel</span>
                <span class="tag-item">SPSS</span>
                <span class="tag-item">Python</span>
                <span class="tag-item">資料探勘</span>
                <span class="tag-item">資料倉儲</span>
            </div>
            <div class="info-desc">補充醫療保險,節日福利,定期體檢,年終獎,餐補,交通補助,免費班車,包吃,股票期權,員工旅遊,零食下午茶,五險一金,帶薪年假</div>
        </div>
    </div>
</li>

可以看到,所需要的資訊都在這些程式碼中。

還可以在頁面中獲取職位描述,如下:
data web boss review description

並且進一步獲取到完整的HTML程式碼如下:

<li>

    <div class="job-primary">


        <div class="info-primary">
            <div class="primary-wrapper">
                <div class="primary-box" href="/job_detail/7271f2f28169375a1nR42t-6GFpQ.html?ka=search_list_1"
                    data-jid="7271f2f28169375a1nR42t-6GFpQ" data-itemid="1" data-lid="nlp-aqyTkPDQjXA.search.1"
                    data-jobid="102127880" data-index="0" ka="search_list_1" target="_blank">
                    <div class="job-title">
                        <span class="job-name"><a href="/job_detail/7271f2f28169375a1nR42t-6GFpQ.html" title="資料分析"
                                target="_blank" ka="search_list_jname_1" data-jid="7271f2f28169375a1nR42t-6GFpQ"
                                data-itemid="1" data-lid="nlp-aqyTkPDQjXA.search.1" data-jobid="102127880"
                                data-index="0">資料分析</a></span>
                        <span class="job-area-wrapper">
                            <span class="job-area">北京·朝陽區·鳥巢</span>
                        </span>
                        <span class="job-pub-time"></span>
                    </div>
                    <div class="job-limit clearfix">
                        <span class="red">50-80K·14薪</span>
                        <p>3-5年<em class="vline"></em>本科</p>
                        <div class="info-publis">
                            <h3 class="name"><img class="icon-chat"
                                    src="https://z.zhipin.com/web/geek/resource/icon-chat-v2.png">曹先生<em
                                    class="vline"></em>資料探勘</h3>
                        </div>
                        <button class="btn btn-startchat" href="javascript:;"
                            data-url="/wapi/zpgeek/friend/add.json?jobId=7271f2f28169375a1nR42t-6GFpQ&amp;lid=nlp-aqyTkPDQjXA.search.1"
                            redirect-url="/web/geek/chat?id=495f7159c0c8664a1nFz39m8EA~~">
                            <img class="icon-chat icon-chat-hover"
                                src="https://z.zhipin.com/web/geek/resource/icon-chat-hover-v2.png" alt="">
                            <span>立即溝通</span>
                        </button>
                    </div>
                    <div class="info-detail" style="top: -307.1px;">
                        <div class="info-detail-top">
                            <div class="detail-top-left">
                                <div class="detail-top-title">資料分析</div>
                                <div class="detail-top-text">京東集團 · 資料探勘: 曹先生</div>
                                <a href="javascript:;" ka="popjob_interest_tosign_7271f2f28169375a1nR42t-6GFpQ"
                                    data-url="/geek/tag/jobtagupdate.json?jobId=7271f2f28169375a1nR42t-6GFpQ&amp;expectId=&amp;tag=4&amp;lid=nlp-aqyTkPDQjXA.search.1"
                                    class="link-like " job-id="495f7159c0c8664a1nFz39m8EA~~">感興趣</a>
                            </div>
                            <div class="detail-top-right detail-top-right2">
                                <div class="code-des">掃一掃,隨時與BOSS開聊</div>
                                <div class="code-icon"></div>
                            </div>
                        </div>
                        <div class="detail-bottom">
                            <div class="detail-bottom-title">職位描述</div>
                            <div class="detail-bottom-text">
                                職位描述<br>1、 分析研究人物誌,通過對海量資料的分析挖掘,提取使用者特徵、行為軌跡;<br>2、 參與演演算法研發工作,提升演算法系統的效能和業務指標;<br>3、
                                梳理、對接不同業務線的臨時資料需求,並抽象出客製化化資料產品; <br>4、 結合專案需求,綜合利用京東商城資料,搭建客製化化指數模型;<br>5、
                                負責為產品運營提供資料分析支援,如產品分析、使用者分析、運營分析等,並根據分析結果提出可落地的策略建議;<br>6、
                                積極推進跨部門合作,配合各類專案如期保質保量實施執行。<br>崗位要求<br>1、 本科及以上學歷,統計學、資料、計算機相關專業優先考慮;<br>2、
                                兩年及以上網際網路資料分析從業經歷,有電商類公司經驗者優先,有綜合指數構建經驗者優先;<br>3、
                                能獨立進行資料處理,撰寫專項分析報告,掌握常用的分類、聚類、預測、關聯規則、序列模式等挖掘演演算法;<br>4、
                                學習能力強,具備良好的溝通能力,能充分理解業務邏輯和目的,有清晰的資料分析思路和方法;<br>5、
                                資料敏感度高,善於從資料中發現問題,並可給出一定的解決方案;<br>6、
                                精通SQL、EXCEL,熟悉SPSS、SAS、Clementine、R、python等任一種專業資料分析工具,有Hadoop、Hive、Spark等使用經驗者優先。<br>7、
                                有迴歸、聚類、分類、神經網路、NLP、最佳化理論等相關理論基礎和專案應用者優先
                            </div>
                        </div>
                    </div>
                </div>
            </div>
            <div class="info-company">
                <div class="company-text">
                    <h3 class="name"><a href="/gongsi/33e052361693f8371nF-3d25.html" title="京東集團招聘"
                            ka="search_list_company_1_custompage" target="_blank">京東集團</a></h3>
                    <p><a href="/i100001/" class="false-link" target="_blank"
                            ka="search_list_company_industry_1_custompage" title="電子商務行業招聘資訊">電子商務</a><em
                            class="vline"></em>已上市<em class="vline"></em>10000人以上</p>
                </div>
                <a href="/gongsi/33e052361693f8371nF-3d25.html" ka="search_list_company_1_custompage_logo"
                    target="_blank"><img class="company-logo"
                        src="https://img.bosszhipin.com/beijin/mcs/bar/20191129/3cdf5ba2149e309b38868b62ae9c22cabe1bd4a3bd2a63f070bdbdada9aad826.jpg?x-oss-process=image/resize,w_100,limit_0"
                        alt=""></a>
            </div>
        </div>
        <div class="info-append clearfix">
            <div class="tags">
                <span class="tag-item">Excel</span>
                <span class="tag-item">SPSS</span>
                <span class="tag-item">Python</span>
                <span class="tag-item">資料探勘</span>
                <span class="tag-item">資料倉儲</span>
            </div>
            <div class="info-desc">補充醫療保險,節日福利,定期體檢,年終獎,餐補,交通補助,免費班車,包吃,股票期權,員工旅遊,零食下午茶,五險一金,帶薪年假</div>
        </div>
    </div>

</li>

還可以進一步存取職位詳情如下:
data web boss review list

2.資料獲取

先匯入所需要的庫,如下:

## Import the necessary packages
from bs4 import BeautifulSoup as bs
import urllib
import re
import pandas as pd
import requests

如需本節同步ipynb和資料檔案,可以直接點選加QQ群 Python極客部落963624318 在群資料夾商業資料分析從入門到入職中下載即可。

使用requests庫模擬請求:

response = requests.get('https://www.zhipin.com/job_detail/?query=%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90&city=100010000&industry=&position=')

獲取返回的相應內容,如下:

display(response.content[:300], response.text, response.encoding)

輸出:

b'<!DOCTYPE html>\n<html>\n    <head>\n        <meta charset="utf-8" />\n        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />\n        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no" />\n        <title>\xe8\xaf\xb7\xe7\xa8\x8d\xe5\x90'

'<!DOCTYPE html>\n<html>\n    <head>\n        <meta charset="utf-8" />\n        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />\n        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no" />\n        <title>请ç¨\x8då\x90\x8e</title>\n        <style>\n            html,\n            body {\n                margin: 0;\n                width: 100%;\n                height: 100%;\n            }\n            @keyframes bossLoading {\n                0% {\n                    transform: translate3d(0, 0, 0);\n                }\n                50% {\n                    transform: translate3d(0, -10px, 0);\n                }\n            }\n            .data-tips {\n                text-align: center;\n                height: 100%;\n                position: relative;\n                background: #fff;\n                top: 50%;\n                margin-top: -37px;\n            }\n            .data-tips .boss-loading {\n                width: 100%;\n            }\n            .data-tips .boss-loading p {\n                margin-top: 10px;\n                color: #9fa3b0;\n            }\n            .boss-loading .component-b,\n            .boss-loading .component-s1,\n            .boss-loading .component-o,\n            .boss-loading .component-s2 {\n                display: inline-block;\n                width: 40px;\n                height: 42px;\n                line-height: 42px;\n                font-family: Helvetica Neue,Helvetica,Arial,Hiragino Sans GB,Hiragino Sans GB W3,Microsoft YaHei UI,Microsoft YaHei,WenQuanYi Micro Hei,sans-serif;\n                font-weight: bolder;\n                font-size: 40px;\n                color: #eceef2;\n                vertical-align: top;\n                -webkit-animation-fill-mode: both;\n                -webkit-animation: bossLoading 0.6s infinite linear alternate;\n                -moz-animation: bossLoading 0.6s infinite linear alternate;\n                animation: bossLoading 0.6s infinite linear alternate;\n            }\n            .boss-loading .component-o {\n                -webkit-animation-delay: 0.1s;\n                -moz-animation-delay: 0.1s;\n                animation-delay: 0.1s;\n            }\n            .boss-loading .component-s1 {\n                -webkit-animation-delay: 0.2s;\n                -moz-animation-delay: 0.2s;\n                animation-delay: 0.2s;\n            }\n            .boss-loading .component-s2 {\n                -webkit-animation-delay: 0.3s;\n                -moz-animation-delay: 0.3s;\n                animation-delay: 0.3s;\n            }\n        </style>\n    </head>\n    <body>\n        <div class="data-tips">\n            <div class="tip-inner">\n                <div class="boss-loading">\n                    <span class="component-b">B</span><span class="component-o">O</span><span class="component-s1">S</span><span class="component-s2">S</span>\n                    <p class="gray">æ\xad£å\x9c¨å\x8a\xa0è½½ä¸\xad...</p>\n                </div>\n            </div>\n        </div>\n        <script>\n            var securityPageName="securityCheck";!function(){var a=new Image;a.src="https://t.zhipin.com/f.gif?pk="+securityPageName+"&r="+document.referrer}(),function(){function e(c){var l,m,n,o,p,q,r,e=function(){var a=location.hostname;return"localhost"===a||/^(\\d+\\.){3}\\d+$/.test(a)?a:"."+a.split(".").slice(-2).join(".")}(),f=function(a,b){var f=document.createElement("script");f.setAttribute("type","text/javascript"),f.setAttribute("charset","UTF-8"),f.οnlοad=f.onreadystatechange=function(){d&&"loaded"!=this.readyState&&"complete"!=this.readyState||b()},f.setAttribute("src",a),"IFRAME"!=c.tagName?c.appendChild(f):c.contentDocument?c.contentDocument.body?c.contentDocument.body.appendChild(f):c.contentDocument.documentElement.appendChild(f):c.document&&(c.document.body?c.document.body.appendChild(f):c.document.documentElement.appendChild(f))},g=function(a){var b=new RegExp("(^|&)"+a+"=([^&]*)(&|$)"),c=window.location.search.substr(1).match(b);return null!=c?unescape(c[2]):null},h={get:function(a){var b,c=new RegExp("(^| )"+a+"=([^;]*)(;|$)");return(b=document.cookie.match(c))?unescape(b[2]):null},set:function(a,b,c,d,e){var g,f=a+"="+encodeURIComponent(b);c&&(g=new Date(c).toGMTString(),f+=";expires="+g),f=d?f+";domain="+d:f,f=e?f+";path="+e:f,document.cookie=f}},i=function(a){window.location.replace(a)},j=function(a,c){c||a.indexOf("security-check.html")>-1?i(c):i(a);var d=new Image;d.src="https://t.zhipin.com/f.gif?pk="+securityPageName+"&ca=securityCheckJump_"+Math.round(((new Date).getTime()-b)/1e3)+"&r="+document.referrer};window.location.href,l=g("seed")||"",m=g("ts"),n=g("name"),o=g("callbackUrl"),p=g("srcReferer")||"","null"!==n&&l&&n&&o||(q=new Image,q.src="https://t.zhipin.com/f.gif?pk="+securityPageName+"&ca=securityCheckUrlFile&url="+window.location.href),l&&m&&n&&(r=setInterval(function(){a++,a>5&&clearInterval(r);var c=new Image;c.src="https://t.zhipin.com/f.gif?pk="+securityPageName+"&ca=securityCheckTimer_"+Math.round(((new Date).getTime()-b)/1e3)+"&r="+document.referrer},1e4),f("security-js/"+n+".js",function(){var n,a=(new Date).getTime()+2304e5,d="",f={},g=window.ABC||c.contentWindow.ABC;try{d=(new g).z(l,parseInt(m)+1e3*60*(480+(new Date).getTimezoneOffset()))}catch(k){}d&&o?(h.set("__zp_stoken__",d,a,e,"/"),"undefined"!=typeof window.wst&&"function"==typeof wst.postMessage&&(f={name:"setWKCookie",params:{url:e,name:"__zp_stoken__",value:encodeURIComponent(d),expiredate:a,path:"/"}},window.wst.postMessage(JSON.stringify(f))),j(p,o)):(n=new Image,n.src="https://t.zhipin.com/f.gif?pk="+securityPageName+"&ca=securityCheckNoCode_"+Math.round(((new Date).getTime()-b)/1e3)+"&r="+document.referrer,i("/"))}))}function j(a){if(!f&&!g&&document.addEventListener)return document.addEventListener("DOMContentLoaded",a,!1);if(!(h.push(a)>1))if(f)!function(){try{document.documentElement.doScroll("left"),i()}catch(a){setTimeout(arguments.callee,0)}}();else if(g)var b=setInterval(function(){/^(loaded|complete)$/.test(document.readyState)&&(clearInterval(b),i())},0)}var d,f,g,h,i,a=0,b=(new Date).getTime(),c=window.navigator.userAgent;c.indexOf("MSIE ")>-1&&(d=!0),f=!(!window.attachEvent||window.opera),g=/webkit\\/(\\d+)/i.test(navigator.userAgent)&&RegExp.$1<525,h=[],i=function(){for(var a=0;a<h.length;a++)h[a]()},j(function(){var b,a=window.navigator.userAgent.toLowerCase();return"micromessenger"==a.match(/micromessenger/i)||"wkwebview"==a.match(/wkwebview/i)?(e(document.getElementsByTagName("head").item(0)),void 0):(b=document.createElement("iframe"),b.style.height=0,b.style.width=0,b.style.margin=0,b.style.padding=0,b.style.border="0 none",b.name="zhipinFrame",b.src="about:blank",b.attachEvent?b.attachEvent("onload",function(){e(b)}):b.οnlοad=function(){e(b)},(document.body||document.documentElement).appendChild(b),void 0)})}();\n\n            var _hmt = _hmt || [];\n            (function() {\n                var hm = document.createElement("script");\n                hm.src = "https://hm.baidu.com/hm.js?194df3105ad7148dcf2b98a91b5e727a";\n                var s = document.getElementsByTagName("script")[0];\n                s.parentNode.insertBefore(hm, s);\n            })();\n        </script>\n    </body>\n</html>\n'

'ISO-8859-1'

其中,response.content用來獲取相應的原始內容,response.text用來獲取經過編碼渲染後的內容,response. encoding是用於獲取編碼方式的。

但是顯然,頁面資訊並沒有顯示完整,這是因為一般請求並不只是傳入連結,還有一些其他的請求資訊,如User-Agent、Referer、Cookie等。
如下:

header = {
    'Cookie': 'lastCity=100010000; __g=-; Hm_lvt_194df3105ad7148dcf2b98a91b5e727a=1601602464; Hm_lpvt_194df3105ad7148dcf2b98a91b5e727a=1601603899; __zp_stoken__=cb83bGgJhaiViDXQAITlxUxFkf1pCNVEpEwUhZztsI15sAmVWQCkEKnUxcRpDISgGPFcSd0wHd11lKGM1Pn80J0RbEhEvayU6GXYcUwQVSThRFWM6IQ4gLwRCG2wAHE59OgYYZFcOBlsQA3VWJQ%3D%3D; __c=1601602461; __l=l=%2Fwww.zhipin.com%2F&r=&g=&friend_source=0&friend_source=0; __a=80430348.1601602461..1601602461.7.1.7.7',
    'Host': 'www.zhipin.com',
    'Referer': 'https://www.zhipin.com/job_detail/?query=%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90&city=100010000&industry=&position=',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:78.0) Gecko/20100101 Firefox/78.0'
}
res = requests.get('https://www.zhipin.com/job_detail/?query=%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90&city=100010000&industry=&position=', headers=header)
res.text

此時輸出的資訊更加完整。

同時還可以將請求到的內容儲存到檔案中,如下:

html_file = open('bosspage.html','w', encoding='utf-8')
html_file.write(res.text)
html_file.close()

執行後,可以看到當前目錄下已經多了一個檔案bosspage.html,也可以在瀏覽器中開啟該檔案,頁面是和之前的頁面一樣的效果,所需要的資訊也儲存在HTML程式碼中。

3.提取列表資訊

有了網頁程式碼之後,就可以提取資訊了,之前是用字串方式提取字串的,現在選擇BeautifulSoup來選擇所需要的資訊。

簡單使用如下:

html = """
<html><head><title>The Dormouse's title</title></head>
<body>
<p class="title" name="dromouse"><b>The Dormouse's story</b></p>
<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1"><!-- Elsie --></a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p>
</body>
</html>
"""

soup_first = bs(html, 'html.parser')
soup_first.prettify()

輸出:

'<html>\n <head>\n  <title>\n   The Dormouse\'s title\n  </title>\n </head>\n <body>\n  <p class="title" name="dromouse">\n   <b>\n    The Dormouse\'s story\n   </b>\n  </p>\n  <p class="story">\n   Once upon a time there were three little sisters; and their names were\n   <a class="sister" href="http://example.com/elsie" id="link1">\n    <!-- Elsie -->\n   </a>\n   ,\n   <a class="sister" href="http://example.com/lacie" id="link2">\n    Lacie\n   </a>\n   and\n   <a class="sister" href="http://example.com/tillie" id="link3">\n    Tillie\n   </a>\n   ;\nand they lived at the bottom of a well.\n  </p>\n  <p class="story">\n   ...\n  </p>\n </body>\n</html>\n'

可以獲取標籤中所有的文字,如下:

soup_first.text

輸出:

"\nThe Dormouse's title\n\nThe Dormouse's story\nOnce upon a time there were three little sisters; and their names were\n,\nLacie and\nTillie;\nand they lived at the bottom of a well.\n...\n\n\n"

也可以獲取標籤的屬性,如下:

all_a = soup_first.find_all("a")
all_a[0]["href"]

輸出:

'http://example.com/elsie'

可以看到,獲取到了a標籤的href屬性,即連結。

還可以獲取所有連結,如下:

[a['href'] for a in soup_first.find_all("a")]

輸出:

['http://example.com/elsie',
 'http://example.com/lacie',
 'http://example.com/tillie']

還有其他一些用法:

display(soup_first.title,soup_first.head,soup_first.a,soup_first.p.string,soup_first.find_all("a"))

輸出:

<title>The Dormouse's title</title>

<head><title>The Dormouse's title</title></head>

<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>

"The Dormouse's story"

[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>,
 <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
 <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

使用BOSS直聘頁面對BeautifulSoup進行初始化:

soup = bs(res.text, 'lxml')
soup.prettify()

定位到所需要的資訊,如下:

all_jobs = soup.find_all("div", class_="job-primary")
all_jobs[0]

輸出:

<div class="job-primary">
<div class="info-primary">
<div class="primary-wrapper">
<div class="primary-box" data-index="0" data-itemid="1" data-jid="7271f2f28169375a1nR42t-6GFpQ" data-jobid="102127880" data-lid="nlp-arJU8s0LBOW.search.1" href="/job_detail/7271f2f28169375a1nR42t-6GFpQ.html" ka="search_list_1" target="_blank">
<div class="job-title">
<span class="job-name"><a data-index="0" data-itemid="1" data-jid="7271f2f28169375a1nR42t-6GFpQ" data-jobid="102127880" data-lid="nlp-arJU8s0LBOW.search.1" href="/job_detail/7271f2f28169375a1nR42t-6GFpQ.html" ka="search_list_jname_1" target="_blank" title="資料分析">資料分析</a></span>
<span class="job-area-wrapper">
<span class="job-area">北京·朝陽區·鳥巢</span>
</span>
<span class="job-pub-time"></span>
</div>
<div class="job-limit clearfix">
<span class="red">50-80K·14薪</span>
<p>3-5年<em class="vline"></em>本科</p>
<div class="info-publis">
<h3 class="name"><img class="icon-chat" src="https://z.zhipin.com/web/geek/resource/icon-chat-v2.png"/>曹先生<em class="vline"></em>資料探勘</h3>
</div>
<button class="btn btn-startchat" data-url="/wapi/zpgeek/friend/add.json?jobId=7271f2f28169375a1nR42t-6GFpQ&amp;lid=nlp-arJU8s0LBOW.search.1" href="javascript:;" redirect-url="/web/geek/chat?id=495f7159c0c8664a1nFz39m8EA~~">
<img alt="" class="icon-chat icon-chat-hover" src="https://z.zhipin.com/web/geek/resource/icon-chat-hover-v2.png"/>
<span>立即溝通</span>
</button>
</div>
<div class="info-detail"></div>
</div>
</div>
<div class="info-company">
<div class="company-text">
<h3 class="name"><a href="/gongsi/33e052361693f8371nF-3d25.html" ka="search_list_company_1_custompage" target="_blank" title="京東集團招聘">京東集團</a></h3>
<p><a class="false-link" href="/i100001/" ka="search_list_company_industry_1_custompage" target="_blank" title="電子商務行業招聘資訊">電子商務</a><em class="vline"></em>已上市<em class="vline"></em>10000人以上</p>
</div>
<a href="/gongsi/33e052361693f8371nF-3d25.html" ka="search_list_company_1_custompage_logo" target="_blank"><img alt="" class="company-logo" src="https://img.bosszhipin.com/beijin/mcs/bar/20191129/3cdf5ba2149e309b38868b62ae9c22cabe1bd4a3bd2a63f070bdbdada9aad826.jpg?x-oss-process=image/resize,w_100,limit_0"/></a>
</div>
</div>
<div class="info-append clearfix">
<div class="tags">
<span class="tag-item">Excel</span>
<span class="tag-item">SPSS</span>
<span class="tag-item">Python</span>
<span class="tag-item">資料探勘</span>
<span class="tag-item">資料倉儲</span>
</div>
<div class="info-desc">補充醫療保險,節日福利,定期體檢,年終獎,餐補,交通補助,免費班車,包吃,股票期權,員工旅遊,零食下午茶,五險一金,帶薪年假</div>
</div>
</div>

可以看到,這是一條職位的詳細資訊。

先對一條職位資訊進行提取:

base_boss_url ="https://www.zhipin.com"
job_link= base_boss_url + all_jobs[0].a["href"]
job_title = all_jobs[0].a.text
job_salary = all_jobs[0].find('span',class_='red').text
other_detail = all_jobs[0].find("div", class_="info-detail").text
company_url = base_boss_url + all_jobs[0].select(".info-company")[0].a["href"]
company = all_jobs[0].select(".info-company")[0].a.text
company_info = all_jobs[0].select(".info-company")[0].p.text
publish_info = all_jobs[0].find("div",class_="info-publis").h3.text

"{}-{}-{}-{}-{}-{}-{}-{}".format(job_link,job_title,job_salary,other_detail,company_url,company,company_info,publish_info)

輸出:

'https://www.zhipin.com/job_detail/7271f2f28169375a1nR42t-6GFpQ.html-資料分析-50-80K·14薪--https://www.zhipin.com/gongsi/33e052361693f8371nF-3d25.html-京東集團-電子商務已上市10000人以上-曹先生資料探勘'

顯然,已經提取出1個職位的詳情資訊。

進一步通過for迴圈提取當前頁中所有職位的資訊,如下:

jobs_index = []
for job_ in all_jobs:
    job_link= base_boss_url + job_.a["href"]
    job_title = job_.a.text
    job_salary = job_.find('span',class_='red').text
    other_detail = job_.find("div", class_="info-detail").text
    company_url = base_boss_url + job_.select(".info-company")[0].a["href"]
    company = job_.select(".info-company")[0].a.text
    company_info = job_.select(".info-company")[0].p.text
    publish_info = job_.find("div",class_="info-publis").h3.text
    jobs_index.append([job_link,job_title,job_salary,other_detail,company_url,company,company_info,publish_info])
    
jobs_index

輸出:

[['https://www.zhipin.com/job_detail/7271f2f28169375a1nR42t-6GFpQ.html',
  '資料分析',
  '50-80K·14薪',
  '',
  'https://www.zhipin.com/gongsi/33e052361693f8371nF-3d25.html',
  '京東集團',
  '電子商務已上市10000人以上',
  '曹先生資料探勘'],
 ['https://www.zhipin.com/job_detail/1fe1d55e100e19d43nR509m-E1Q~.html',
  '資料分析',
  '18-35K·15薪',
  '',
  'https://www.zhipin.com/gongsi/918159f26789c3891nV53dQ~.html',
  '小紅書',
  '網際網路D輪及以上1000-9999人',
  '劉先生商業資料中臺'],
 ['https://www.zhipin.com/job_detail/4423d7c2eda602351nR-09u0EVs~.html',
  '資料分析',
  '25-40K·16薪',
  '',
  'https://www.zhipin.com/gongsi/fa2f92669c66eee31Hc~.html',
  'BOSS直聘',
  '人力資源服務D輪及以上1000-9999人',
  '艾力凡先生資料分析'],
 ['https://www.zhipin.com/job_detail/9c2e41ed166d74bd03J-29u0F1s~.html',
  '商業資料分析',
  '25-40K·15薪',
  '',
  'https://www.zhipin.com/gongsi/980f48937a13792b1nd63d0~.html',
  '滴滴出行',
  '行動網際網路D輪及以上1000-9999人',
  '王先生商業分析高階經理'],
 ['https://www.zhipin.com/job_detail/27d069780b8cc5c53nV62dS8EFE~.html',
  '資料分析崗',
  '20-40K·14薪',
  '',
  'https://www.zhipin.com/gongsi/6e19637143bd80ad1HV_3N26GQ~~.html',
  '建信金科',
  '銀行不需要融資1000-9999人',
  '王先生架構師/研究員'],
 ...
 ['https://www.zhipin.com/job_detail/4c408eec4076e9d80nV73NW5FVU~.html',
  '資料分析師',
  '30-50K·16薪',
  '',
  'https://www.zhipin.com/gongsi/ea9c5680f57d53d71HV90ty5.html',
  '拼多多',
  '行動網際網路已上市1000-9999人',
  '王女士商業化部資料團隊leader'],
 ['https://www.zhipin.com/job_detail/9f44d60c7097321033142tu4FVI~.html',
  '業務資料分析',
  '20-30K',
  '',
  'https://www.zhipin.com/gongsi/92674acda23901841nd_292-EQ~~.html',
  '車好多集團',
  '網際網路D輪及以上10000人以上',
  '李女士HR'],
 ['https://www.zhipin.com/job_detail/a6df576d9539ad810HN439i7Flo~.html',
  '資料分析師',
  '30-50K·14薪',
  '',
  'https://www.zhipin.com/gongsi/48e6b3630a48ccdb03N-2di9.html',
  '分享動力',
  '網際網路不需要融資500-999人',
  '陳女士招聘者'],
 ['https://www.zhipin.com/job_detail/89713a5a1647e44e0XF63dW8F1Y~.html',
  '資料分析師',
  '20-30K·13薪',
  '',
  'https://www.zhipin.com/gongsi/d6f0653b1a4d44740XB_29W0.html',
  '猿輔導',
  '線上教育D輪及以上1000-9999人',
  '毛女士hrbp高階經理'],
 ['https://www.zhipin.com/job_detail/7585af83791f132833F639u7Flo~.html',
  '資料分析師',
  '15-25K',
  '',
  'https://www.zhipin.com/gongsi/f12428f4426b92a033V52tU~.html',
  '360',
  '行動網際網路已上市1000-9999人',
  '張女士HRBP']]

顯然,此時提取出的都是有用的資訊。

由於有多頁,因此需要翻頁獲取每一頁的資訊,此時需要獲取到頁面中的下一頁連結,如下:

next_page = base_boss_url + soup.find("a", class_="next")['href']
next_page

輸出:

'https://www.zhipin.com/c100010000/?query=%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90&page=2'

顯然,獲取到了下一頁的連結。

進一步用函數的形式實現:

def extract_jobs(page):
    page_soup = bs(page, 'lxml')
    all_jobs = page_soup.find_all("div", class_="job-primary")
    jobs_index = []
    print("parseing page ",page_soup.title.text)
    for job_ in all_jobs:
        job_link= base_boss_url + job_.a["href"]
        job_title = job_.a.text
        job_salary = job_.find('span',class_='red').text
        other_detail = job_.find("div", class_="info-detail").text
        company_url = base_boss_url + job_.select(".info-company")[0].a["href"]
        company = job_.select(".info-company")[0].a.text
        company_info = job_.select(".info-company")[0].p.text
        publish_info = job_.find("div",class_="info-publis").h3.text
        jobs_index.append([job_link,job_title,job_salary,other_detail,company_url,company,company_info,publish_info])

    next_page = base_boss_url + soup.find("a", class_="next")['href']
    print("next page is ",next_page)
    return jobs_index, next_page

再回圈實現爬取多頁:

next_page = "https://www.zhipin.com/job_detail/?query=資料分析&city=100010000&industry=&position="
header = {
    'Cookie': 'Hm_lvt_194df3105ad7148dcf2b98a91b5e727a=1601622554; lastCity=100010000; __g=-; toUrl=https%3A%2F%2Fwww.zhipin.com%2Fc100010000%2F%3Fquery%3D%25E6%2595%25B0%25E6%258D%25AE%25E5%2588%2586%25E6%259E%2590%26page%3D2%26ka%3Dpage-2; t=CPzVdSehDMWYI0ch; wt=CPzVdSehDMWYI0ch; _bl_uid=70kkOfndrz2x09b2wqjXvwRw7CXh; __c=1601622556; __l=l=%2Fwww.zhipin.com%2Fjob_detail%2F%3Fquery%3D%25E6%2595%25B0%25E6%258D%25AE%25E5%2588%2586%25E6%259E%2590%26city%3D100010000%26industry%3D%26position%3D&r=&g=&friend_source=0&friend_source=0; __a=10559958.1598103978.1598103978.1601622556.20.2.19.20; Hm_lpvt_194df3105ad7148dcf2b98a91b5e727a=1601625518; __zp_stoken__=cb83bGmgSGWkpKCwDKD94UGNacAUEGlI0IiUsTFEZOkpsdHcVUH9dZWN0U3hoOykGPFcSd0wHeyVlID01OwRMXh5NPCtDNBRnZXAZTAIVSThRFWM6IQ86BGZgXnpPRRhtOgYYZFcOBlsQA3VWJQ%3D%3D',
    'Host': 'www.zhipin.com',
    'Referer': 'https://www.zhipin.com/',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36',
}
counter = 0
all_jobs = []
while next_page != "javascript:;":
    print("start to fecth url ",next_page)
    boss_response = requests.get(next_page, headers=header)
    jobs, next_page = extract_jobs(boss_response.text)
    counter +=1

    if len(jobs) > 0:
        all_jobs = all_jobs + jobs
    
    if counter > 3:
        break
    time.sleep(random.randint(5,12))

輸出如下:

start to fecth url  https://www.zhipin.com/job_detail/?query=資料分析&city=100010000&industry=&position=
parseing page  「全國資料分析招聘」-2020年全國資料分析最新人才招聘資訊 - BOSS直聘
start to fecth url  https://www.zhipin.com/c100010000/?query=%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90&page=2
parseing page  「全國資料分析招聘」-2020年全國資料分析最新人才招聘資訊 - BOSS直聘
start to fecth url  https://www.zhipin.com/c100010000/?query=%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90&page=3
parseing page  「全國資料分析招聘」-2020年全國資料分析最新人才招聘資訊 - BOSS直聘
start to fecth url  https://www.zhipin.com/c100010000/?query=%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90&page=4
parseing page  「全國資料分析招聘」-2020年全國資料分析最新人才招聘資訊 - BOSS直聘

由於現在BOSS直聘的反爬措施比較嚴厲,因此必須在請求頭中加入Cookie資訊(這是驗證一個使用者的基本資訊),在瀏覽器中Cookie的獲取方法如下:
data web boss get cookie

注意,需要獲取第一條網路請求(即類似於https://www.zhipin.com/job_detail/?query=資料分析&city=100010000&industry=&position=的請求)對應的Cookie,因為不同的請求對應的Cookie可能會有所不同;
並且,一個Cookie一般只能用一次,因此爬取一次應該重新獲取Cookie,最好是註冊使用者之後再獲取Cookie、這樣更有效。
同時為了控制存取頻率,每執行一次翻頁迴圈後,都通過time.sleep()方法暫停執行。

此時檢視獲取到的資料,如下:

all_jobs

輸出:

[['https://www.zhipin.com/job_detail/e1bde0976de53e081nR43dm5EFRU.html',
  '資料分析師(實習)',
  '200-300元/天',
  '',
  'https://www.zhipin.com/gongsi/2e64a887a110ea9f1nRz.html',
  '騰訊',
  '網際網路已上市10000人以上',
  '黃女士HRBP'],
 ['https://www.zhipin.com/job_detail/062a0a30e8b663103nJy2N65E1E~.html',
  '【校招】資料分析師',
  '20-30K·16薪',
  '',
  'https://www.zhipin.com/gongsi/fa2f92669c66eee31Hc~.html',
  'BOSS直聘',
  '人力資源服務D輪及以上1000-9999人',
  'BOSS直聘校招校園招聘'],
 ['https://www.zhipin.com/job_detail/5b132f8291af536d3nN42di7F1Y~.html',
  '週末雙休 資料分析',
  '7-12K',
  '',
  'https://www.zhipin.com/gongsi/aa07960c21a559c61nV_3N24GFs~.html',
  '北京磐程',
  '電子商務100-499人',
  '王女士人事經理'],
...
 ['https://www.zhipin.com/job_detail/3bcf1023eea94e363nN_3d65GFM~.html',
  '資料分析師',
  '7-8K',
  '',
  'https://www.zhipin.com/gongsi/c58313ff6a0317b10HN83d-0.html',
  '堅果動力',
  '遊戲A輪100-499人',
  '李強制作人'],
 ['https://www.zhipin.com/job_detail/0cf98b59a339fd4603R90tS5FlQ~.html',
  '資料分析師',
  '8-10K',
  '',
  'https://www.zhipin.com/gongsi/90ffbb07580a82d203d73d-5Fw~~.html',
  '北京立言創新科技...',
  '學術/科研未融資0-20人',
  '高曉玲設計師']]

顯然,已經獲取到了需要的資料。

還可以進一步儲存到檔案中,如下:

fout = open('job_data.csv', 'wt')
for info in all_jobs:
    fout.write(",".join(info)+"\n")
fout.close()

執行成功後,列表中會多出一個檔案job_data.csv。

4.獲取職位詳情資料

獲取職位詳情時,可以利用之前獲取到的詳情連結,通過requests模擬請求並使用BeautifulSoup解析。

先以一個商品詳情連結為例進行探究。
檢視網頁如下:
data web boss job detail review

可以看到,職位詳情都在class為detail-content的div中。

獲取一個職位詳情頁的詳情資訊,如下:

detail_link = all_jobs[0][0]
header = {
    'Cookie': 'lastCity=100010000; Hm_lvt_194df3105ad7148dcf2b98a91b5e727a=1601602464,1601624966; Hm_lpvt_194df3105ad7148dcf2b98a91b5e727a=1601627370; __zp_stoken__=cb83bGmgSGWkpKFVye2gnUGNacAVQeH5ZeQEsTFEZOiALeWBKTX9dZWN0eHZBaRkGPFcSd0wHey9kCTc1M2kdDjAjby9CXQRiHX9yWnsLSThRFWM6IT9oLWhLXnpPRRhwOgYYZFcOBlsQA3VWJQ%3D%3D; __fid=7627d554a7f83f762fe906cbda0d7906; __g=-; __c=1601602461; __l=l=%2Fwww.zhipin.com%2Fc100010000%2F%3Fquery%3D%25E6%2595%25B0%25E6%258D%25AE%25E5%2588%2586%25E6%259E%2590%26page%3D5&r=http%3A%2F%2F127.0.0.1%3A8888%2Fnotebooks%2Fcrawl_boss.ipynb&g=&friend_source=0&friend_source=0; __a=80430348.1601602461..1601602461.23.1.23.23',
    'Host': 'www.zhipin.com',
    'Referer': 'https://www.zhipin.com/',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36',
}
job_detail = requests.request("GET", detail_link,headers=header)
job_soup = bs(job_detail.text,"lxml")
detail_text = job_soup.find("div",class_="job-sec")

detail_text.text

輸出:

'\n職位描述\n\n                                【騰訊視訊號】資料分析-日常實習生【僅接受985/211或海外大學對口專業簡歷】崗位職責負責微信視訊號產品的各項資料分析相關工作崗位要求1. 資料科學/統計/數學專業/信管/計算機等相關專業本科及以上學歷;2. 良好的資料結構和演演算法基礎,優秀的編碼能力;3.資料驅動,熟練使用sql、excel,能高效利用資料指導並優化方案,Python 或 R(必須),SQL(必須),Tableau(加分,建議)Excel(必須)具有海量資料處理經驗;4. 具有良好的溝通能力、坦誠直接、重視團隊合作;5.有才藝其他興趣愛好,網際網路公司同樣實習經歷,自我經營賬號的優先6. 一週實習4天以上,能夠立即到崗,實習3個月以上7. 必須是有學籍的在校生,優先考慮2021屆和2021屆以後畢業同學。其他:1.地點:北京線下;2.待遇:高額薪水,差旅交通報銷,免費三餐,大空間,團隊氛圍nice\n                            \n'

對文字進行進一步的處理:

detail_text = detail_text.text.replace("\n","").replace(" ","")
detail_text

輸出:

'職位描述【騰訊視訊號】資料分析-日常實習生【僅接受985/211或海外大學對口專業簡歷】崗位職責負責微信視訊號產品的各項資料分析相關工作崗位要求1.資料科學/統計/數學專業/信管/計算機等相關專業本科及以上學歷;2.良好的資料結構和演演算法基礎,優秀的編碼能力;3.資料驅動,熟練使用sql、excel,能高效利用資料指導並優化方案,Python或R(必須),SQL(必須),Tableau(加分,建議)Excel(必須)具有海量資料處理經驗;4.具有良好的溝通能力、坦誠直接、重視團隊合作;5.有才藝其他興趣愛好,網際網路公司同樣實習經歷,自我經營賬號的優先6.一週實習4天以上,能夠立即到崗,實習3個月以上7.必須是有學籍的在校生,優先考慮2021屆和2021屆以後畢業同學。其他:1.地點:北京線下;2.待遇:高額薪水,差旅交通報銷,免費三餐,大空間,團隊氛圍nice'

顯然,頁面美觀了很多。

進一步通過迴圈獲取多個詳情連結的詳情資訊:

job_desc=[]
header = {
    'Cookie': 'Cookie: lastCity=100010000; Hm_lvt_194df3105ad7148dcf2b98a91b5e727a=1601602464,1601624966; Hm_lpvt_194df3105ad7148dcf2b98a91b5e727a=1601628313; __zp_stoken__=cb83bGmgSGWkpKF9eQW0WUGNacAVVDB9sNDssTFEZOlIDHXcKU39dZWN0enMzK2IGPFcSd0wHeyAzIGM1LHd1KFU0Y1BHPxZtbHF0XH4cSThRFWM6IUQqX21JXnpPRRhuOgYYZFcOBlsQA3VWJQ%3D%3D; __fid=7627d554a7f83f762fe906cbda0d7906; __g=-; ___gtid=729532789; __c=1601602461; __l=l=%2Fwww.zhipin.com%2Fjob_detail%2F7271f2f28169375a1nR42t-6GFpQ.html%3Fka%3Dsearch_list_jname_1_blank%26lid%3Dnlp-axWMPTPcuB6.search.1&r=http%3A%2F%2F127.0.0.1%3A8888%2Fnotebooks%2Fcrawl_boss.ipynb&g=&friend_source=0&friend_source=0; __a=80430348.1601602461..1601602461.28.1.28.28',
    'Host': 'www.zhipin.com',
    'Referer': 'https://www.zhipin.com/',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36',
}
for job in all_jobs[:4]:
    print(".",end="")
    job_detail = requests.request("GET", job[0], headers=header)
    job_soup = bs(job_detail.text,"lxml")
    detail_text = job_soup.find("div",class_="job-sec").text.replace("\n","").replace(" ","")
    job_desc.append([job[0],detail_text])
    time.sleep(random.random()*3)
    
job_desc

輸出:

[['https://www.zhipin.com/job_detail/e1bde0976de53e081nR43dm5EFRU.html',
  '職位描述【騰訊視訊號】資料分析-日常實習生【僅接受985/211或海外大學對口專業簡歷】崗位職責負責微信視訊號產品的各項資料分析相關工作崗位要求1.資料科學/統計/數學專業/信管/計算機等相關專業本科及以上學歷;2.良好的資料結構和演演算法基礎,優秀的編碼能力;3.資料驅動,熟練使用sql、excel,能高效利用資料指導並優化方案,Python或R(必須),SQL(必須),Tableau(加分,建議)Excel(必須)具有海量資料處理經驗;4.具有良好的溝通能力、坦誠直接、重視團隊合作;5.有才藝其他興趣愛好,網際網路公司同樣實習經歷,自我經營賬號的優先6.一週實習4天以上,能夠立即到崗,實習3個月以上7.必須是有學籍的在校生,優先考慮2021屆和2021屆以後畢業同學。其他:1.地點:北京線下;2.待遇:高額薪水,差旅交通報銷,免費三餐,大空間,團隊氛圍nice'],
 ['https://www.zhipin.com/job_detail/062a0a30e8b663103nJy2N65E1E~.html',
  '職位描述我們的日常工作:1、撰寫code(包括SQL/Shell/Python/R等),提取、加工相關的業務資料2、應用Excel/Python以及一些視覺化工具進行資料視覺化的相關分析3、撰寫分析報告,給出相關的分析結論。主要分為幾個方面:產品業務優化迭代的效果評估,業務假設的資料驗證,業務優化的實施策略的制定,業務可能存在的問題的研究定位,業務發展的戰略方向探索4、與產品、市場、運營、銷售、設計等各個部門進行業務分析結論的溝通,使得資料分析的發現能夠驅動業務的相關優化我們眼中的你:1、這個崗位需要撰寫大量的程式碼,所以希望你不是一個害怕跟程式碼打交道的人。2、這個崗位需要進行資料分析,會需要掌握很多數學相關的知識,所以希望你不是一個從小不喜歡學習數學的人。3、這個崗位涉及大量的與人溝通的工作,所以希望你不是一個過於靦腆的人。4、這個崗位的目的是通過資料分析以理解使用者的行為的內在機理,所以我們希望你是有較好的同理心的人,平時是樂於去理解、並能夠照顧別人感受的人。5、為了能夠更好的理解業務,我們會需要進行廣泛地學習,如經濟學、心理學、系統論、資訊理論等等,所以希望你是一個能夠不斷挑戰自己,對所有的未知都抱有足夠的好奇心的人。6、知識可以學習、能力可以鍛鍊、心智可以培養,最終還是需要你是一個有足夠的事業追求的人。'],
 ['https://www.zhipin.com/job_detail/5b132f8291af536d3nN42di7F1Y~.html',
  '職位描述技能要求:資料分析,資料倉儲1、蒐集行業相關資訊,為相關需求者提供更準確的資料資訊;2、豐富市場分析能力,做出每日分析計劃,熟練掌握各種分析技術;3、對市場、行業、公司運營等提供資料分析計劃,為戰略決策提供支援;4、發表研究成果或分析評論,配合公司的推廣及培訓等工作。5、協助部門經理完善部門管理制度;'],
 ['https://www.zhipin.com/job_detail/b8f8a877b20685010XF62Nm1FVs~.html',
  '職位描述【崗位職責】\u20281、參與原始資料、資料抽取、治理、統計分析到報表展示的全部流程2、深入理解業務,發現業務特徵,進行衍生資料價值挖掘\u2028【任職條件】1、計算機等相關專業者優先;2、熟悉SQL,有使用HIVESQL或者SPARKSQL經驗者,熟練使用java,python或者scala優先;3、思路開闊且靈活,對數位敏感,善於從資料中發現問題並抓住重點;4,具備良好的資料敏感度、良好的邏輯思維,能及時發現和分析資料中隱含的變化和問題;5、良好的邏輯思維能力,能夠從海量資料中發現有價值的規律6、瞭解spark生態,有巨量資料處理經驗的優先7、實習期至少6個月。']]

進一步儲存資料如下:

fout = open('job_desc.csv', 'wt', encoding='gbk')
for info in job_desc:
    fout.write("{},\"{}\"\n".format(info[0],info[1].encode('gbk', 'ignore').decode('gbk', 'ignore')))
fout.close()

再檢視當前目錄,多了檔案為job_desc.csv。

5.詞頻統計和詞雲展示

對於中文詳情的描述,需要先進行分詞,即將文段分成較短的詞語,使用jieba庫分詞,使用前需要先通過命令conda install -c conda-forge jieba進行安裝。

jieba庫的簡單使用如下:

import jieba

fenci = jieba.cut("我在北京上大學,我上的是比清華好的北京大學",cut_all=True)
'/'.join(fenci)

輸出:

'我/在/北京/上/大學///我/上/的/是/比/清華/好/的/北京/北京大學/大學'

進一步使用如下:

fenci = jieba.cut("我在北京讀大學,我讀的是比清華好的北京大學",cut_all=False)
print("/ ".join(fenci))

fenci = jieba.cut("我愛北京天安門,五環比六環少一環,學好python就不是低端勞動力了,嗚嗚",cut_all=False)
print("/ ".join(fenci))

jieba.suggest_freq("六環", tune=True)
fenci = jieba.cut("我愛北京天安門,五環比六環多一環,學好python就不是低端勞動力了,嗚嗚",cut_all=False)
print("/ ".join(fenci))

輸出:

// 北京// 大學// 我讀//// 清華/// 北京大學
我// 北京/ 天安門// 五環/ 比六環少/ 一環// 學好/ python// 不是/ 低端/ 勞動力/// 嗚嗚
我// 北京/ 天安門// 五環// 六環/ 多一環// 學好/ python// 不是/ 低端/ 勞動力/// 嗚嗚

對job_desc進行處理如下:

combined_job_desc = " ".join([j[1] for j in job_desc])
fenci_job_desc = jieba.cut(combined_job_desc,cut_all=False)
space = " ".join(fenci_job_desc)
space

在使用詞雲之前,需要通過命令conda install -c conda-forge wordcloud安裝wordcloud庫。

生成詞雲物件如下:

# 生成WordCloud物件
wc = WordCloud(
#    width=800,
#    height=600,
    background_color="white",  # 設定背景顏色
    max_words=200,  # 詞的最大數(預設為200)
    colormap='viridis',  # string or matplotlib colormap, default="viridis"
    random_state=10,  # 設定有多少種隨機生成狀態,即有多少種配色方案
    font_path='STLITI.TTF' # 設定字型路徑
)
##comments
my_wordcloud = wc.generate(space)

注意:
再進行初始化時,如果是中文詞,需要指定font_path,即字型路徑,並且需要在路徑下有對應的字型檔案。

如需獲取字型檔案進行測試,可以直接點選加QQ群 Python極客部落963624318 ,在群資料夾商業資料分析從入門到入職中下載即可,Windows系統也可以在C:\Windows\Fonts中選擇支援中文的字型複製到專案路徑下。

展示詞雲:

import matplotlib.pyplot as plt
%matplotlib inline

plt.imshow(wc, interpolation="bilinear")
plt.axis("off")
plt.figure()

顯示:
data web boss wordcloud first

可以看到,根據出現關鍵字的次數權重而區分詞語的大小,形成有區分度的詞雲統計。

但是還可以進一步優化,去掉一些重複的、沒有意義的詞語,可以在初始化WordCloud物件時使用stopwords引數忽略掉這些詞。
如下:

wc = WordCloud(
#    width=800,
#    height=600,
    background_color="white",  # 設定背景顏色
#    max_words=200,  # 詞的最大數(預設為200)
    colormap='viridis',  # string or matplotlib colormap, default="viridis"
    random_state=10,  # 設定有多少種隨機生成狀態,即有多少種配色方案
    font_path='STKAITI.TTF',
    stopwords=('資料','資料分析','職位描述','工作','工作內容','職責','工作職責','任職要求','職位','描述','產品','經驗','熟練',
              '進行','運營','相關','以上學歷','使用','工具','本科','提供','負責','業務','熟悉','分析','優先','能力','策略',
              '任職','熟悉','開發','專案','公司','需求','支援','崗位職責','行業','問題','研究','邏輯','具有','搭建','能夠',
              '決策','完成','技術','監控','客戶','基於','方法','設計','瞭解','良好','部門','日常','通過','團隊','網際網路','根據'
            ,'建立','以及','具備','發現','應用','業務部門','制定','掌握','要求','平臺','基礎','以上','推動','體系','管理'
               ,'較強','學習','管理','資格','建議','專業','落地','協助','執行','價值','方案','提出','解決','快速','優秀','參與',
              '方向','改進','建設','評估','研發','資訊','提取','深入','常用','包括','崗位','理解','使用者')
)

my_wordcloud = wc.generate(space)

plt.imshow(wc, interpolation="bilinear")
plt.axis("off")
plt.figure()

顯示:
data web boss wordcloud second

可以看到,相對於之前,有更好的說服力。

還可以進一步統計詞頻,如下:

from jieba import analyse
keywords = analyse.extract_tags(combined_job_desc, topK=300, withWeight=True, allowPOS=('n',))
keywords

輸出:

[('資料', 0.3934027646776582),
 ('業務', 0.35639458308689875),
 ('崗位', 0.19911889163164556),
 ('職位', 0.19509550027518988),
 ('能力', 0.1561817429800633),
...
 ('全部', 0.03118962121886076),
 ('條件', 0.030984291967974684),
 ('基礎', 0.030147029148860763),
 ('技術', 0.0298699821428481),
 ('方面', 0.026981713165253163)]

因為很多技能都是用英文表示的,如MySQL、Python等,因此可以進一步去掉中文,再進行分析。
示意如下:

import re
 
s = 'hi新手oh'
remove_chinese = re.compile(r'[\u4e00-\u9fa5]') #[\u4e00-\u9fa5]是匹配所有中文的正規表示式
remove_chinese.split(s)

輸出:

['hi', '', 'oh']

可以看到,去掉了字串中的中文。
對職位要求詳情去中文如下:

all_english = ''.join(remove_chinese.split(combined_job_desc))
all_english

輸出:

'【】-【985/211】1.;2.,;3.,sql、excel,,PythonR(),SQL(),Tableau(,)Excel();4.、、;5.,,6.4,,37.,20212021。:1.:;2.:,,,,nice :1、code(SQL/Shell/Python/R),、2、Excel/Python3、,。:,,,,4、、、、、,:1、,。2、,,。3、,。4、,,、。5、,,、、、,,。6、、、,。 :,1、,;2、,,;3、、、,;4、,。5、; 【】\u20281、、、、2、,,\u2028【】1、;2、SQL,HIVESQLSPARKSQL,java,pythonscala;3、,,;4,、,;5、,6、spark,7、6。'

同時,很多英文因為大小寫等原因,其實也是表達的同一個意思,如SQLsql,意思一樣,只是大小寫不同,可以合併統計:

combined_job_desc.count("SQL") + combined_job_desc.count("sql")

輸出:

6

分析詞頻如下:

keywords = jieba.analyse.extract_tags(all_english, topK=300, withWeight=True, allowPOS=())
keywords

輸出:

[('SQL', 1.5593175003782607),
 ('Excel', 1.0395450002521738),
 ('985', 0.5197725001260869),
 ('211', 0.5197725001260869),
 ('sql', 0.5197725001260869),
 ('excel', 0.5197725001260869),
 ('PythonR', 0.5197725001260869),
 ('Tableau', 0.5197725001260869),
 ('6.4', 0.5197725001260869),
 ('37', 0.5197725001260869),
 ('20212021', 0.5197725001260869),
 ('nice', 0.5197725001260869),
 ('code', 0.5197725001260869),
 ('Shell', 0.5197725001260869),
 ('Python', 0.5197725001260869),
 ('Python3', 0.5197725001260869),
 ('HIVESQLSPARKSQL', 0.5197725001260869),
 ('java', 0.5197725001260869),
 ('pythonscala', 0.5197725001260869),
 ('spark', 0.5197725001260869)]

此時,詞頻有很大變化。

再畫詞雲如下:

eng_job_desc = jieba.cut(all_english,cut_all=False)
en_space = " ".join(eng_job_desc)

wc_eng = WordCloud(
#    width=1600,
#    height=800,
    background_color="white",  # 設定背景顏色
    max_words=300,  # 詞的最大數(預設為200)
    colormap='viridis',  # string or matplotlib colormap, default="viridis"
    random_state=10,  # 設定有多少種隨機生成狀態,即有多少種配色方案
#     font_path='./fonts/cn/msyh.ttc'
)
##comments
my_wordcloud = wc_eng.generate(en_space)

plt.imshow(wc_eng, interpolation="bilinear")
plt.axis("off")
plt.figure()

顯示:
data web boss wordcloud third

三、王者榮耀列表整合案例

王者榮耀英雄列表網頁為https://pvp.qq.com/web201605/herolist.shtml,展示了英雄的基本資訊。

前面是從網頁中大量資料中找出有用的資訊,但是對於有的網站來說還有更簡單的方式,如有的網站提供了資料API,即通過JSON形式提供資料到前端再渲染顯示,顯然,直接從JSON API中獲取資料更簡單高效。

如王者榮耀英雄列表網頁就使用了JSON資料,如下:
data web king honor json

可以看到,其地址為https://pvp.qq.com/web201605/js/herolist.json,包含了所有英雄的基本資訊,可以下載該JSON檔案,然後就可以直接從檔案中獲取資訊,而不需要再從網頁中解析了,並將這些資訊與網頁中的資訊進行整合、形成更加完善的資訊,並實現可以通過關鍵字查詢相關英雄的資訊。

1.獲取JSON資料

先匯入所需要的庫並獲取到JSON資料,如下:

import json
import requests
from bs4 import BeautifulSoup as bs

rongyao_response = requests.request("GET", "https://pvp.qq.com/web201605/js/herolist.json")
rongyao_response.text

將其儲存到本地檔案,如下:

r = requests.get('https://pvp.qq.com/web201605/js/herolist.json', stream=True)

with open("herolist.json", 'wb') as fd:
    for chunk in r.iter_content(chunk_size=128):
        fd.write(chunk)

對JSON物件的操作可以有json庫實現。
將JSON物件轉化為字典如下:

json_obj = """
{   "zoo_animal": "Lion",
    "food": ["Meat", "Veggies", "Honey"],
    "fur": "Golden",
    "clothes": null, 
    "diet": [{"zoo_animal": "Gazelle", "food":"grass", "fur": "Brown"}]
}
"""

data = json.loads(json_obj)
data

輸出:

{'zoo_animal': 'Lion',
 'food': ['Meat', 'Veggies', 'Honey'],
 'fur': 'Golden',
 'clothes': None,
 'diet': [{'zoo_animal': 'Gazelle', 'food': 'grass', 'fur': 'Brown'}]}

也可以將字典轉化為JSON物件,如下:

json.dumps(data)

輸出:

'{"zoo_animal": "Lion", "food": ["Meat", "Veggies", "Honey"], "fur": "Golden", "clothes": null, "diet": [{"zoo_animal": "Gazelle", "food": "grass", "fur": "Brown"}]}'

也可以讀取JSON檔案轉化為字典,如下:

hero_list = None
with open('herolist.json','rb') as json_data:
    hero_list = json.load(json_data)
    print(hero_list[:5])

輸出:

[{'ename': 105, 'cname': '廉頗', 'title': '正義爆轟', 'new_type': 0, 'hero_type': 3, 'skin_name': '正義爆轟|地獄巖魂'}, {'ename': 106, 'cname': '小喬', 'title': '戀之微風', 'new_type': 0, 'hero_type': 2, 'skin_name': '戀之微風|萬聖前夜|天鵝之夢|純白花嫁|繽紛獨角獸'}, {'ename': 107, 'cname': '趙雲', 'title': '蒼天翔龍', 'new_type': 0, 'hero_type': 1, 'hero_type2': 4, 'skin_name': '蒼天翔龍|忍●炎影|未來紀元|皇家上將|嘻哈天王|白執事|引擎之心'}, {'ename': 108, 'cname': '墨子', 'title': '和平守望', 'new_type': 0, 'hero_type': 2, 'hero_type2': 1, 'skin_name': '和平守望|金屬風暴|龍騎士|進擊墨子號'}, {'ename': 109, 'cname': '妲己', 'title': '魅力之狐', 'pay_type': 11, 'new_type': 0, 'hero_type': 2, 'skin_name': '魅惑之狐|女僕咖啡|魅力維加斯|仙境愛麗絲|少女阿狸|熱情桑巴'}]

列印了前5個檔案的資訊。

獲取每個英雄的型別,如下:

hero_type = ["全部","戰士","法師","坦克","刺客","射手","輔助"]

for hero in hero_list:
    combine_type = []    
    if "hero_type" in hero:
        combine_type.append(hero_type[hero["hero_type"]])
    if "new_type" in hero:
        combine_type.append(hero_type[hero["new_type"]])
    if "hero_type2" in hero:
        combine_type.append(hero_type[hero["hero_type2"]])
    print(hero["cname"] +" "+('|').join(combine_type))

輸出:

廉頗 坦克|全部
小喬 法師|全部
趙雲 戰士|全部|刺客
墨子 法師|全部|戰士
妲己 法師|全部
...
蒙犽 射手|全部
鏡 刺客|全部
蒙恬 戰士|全部
阿古朵 坦克|全部
夏洛特 戰士|戰士

2.獲取網頁英雄資訊

此時再獲取https://pvp.qq.com/web201605/herolist.shtml中的資訊,包括圖片連結等。
嘗試如下:

html_hero_response = requests.request("GET", "https://pvp.qq.com/web201605/herolist.shtml")
html_hero_response.content.decode('gbk')

從輸出中可以看到,輸出中的英雄列表並不完整,與網頁中實際現實的不一致,這可能是因為一部分資訊是通過JavaScript等方式渲染到網頁中的,網頁原始碼中沒有,因此也未請求到。
此時可以使用selenium庫來模擬存取瀏覽器,像人為一樣操作瀏覽器,進而獲取到英雄完整列表。
在使用前需要安裝selenium庫,直接通過conda install -c conda-forge selenium命令即可安裝;
還需要下載驅動,Chrome和FIrefox驅動均可,以Chrome為例,在下載前需要下載Chrome瀏覽器的版本,方式如下:
data web hero Google version

獲取到版本後,再到http://chromedriver.storage.googleapis.com/index.html中選擇與Chrome版本相近的驅動版本,如83.0.4103.14,點選後在當前版本下選擇chromedriver_win32.zip下載,下載解壓後獲取到chromedriver.exe檔案,將其移動到Anaconda安裝目錄下的Scripts目錄下,如E:\Anaconda3\Scripts,如果不是使用的Anaconda,而是普通的Python環境,則移動到Python安裝目錄下的Scripts目錄下,如E:\Python\Python38-32\Scripts目錄下,此時就可以使用selenium進行模擬存取了。

由於官網下載很緩慢,因此我已經將Chrome83.0.4103.14版本對應的驅動下載整理好了,可以直接點選加QQ群 Python極客部落963624318 在群資料夾Python相關安裝包中下載即可,如需其他版本也可以在群裡向群主提出。

模擬存取如下:

from selenium import webdriver

browser = webdriver.Chrome()
browser.get("https://pvp.qq.com/web201605/herolist.shtml")
html = browser.page_source
browser.quit()

執行,如下:
data web king honor selenium simulation

可以看到,有一個Chrome瀏覽器彈出並存取網站,獲取到資訊後自動關閉。

現在使用BeautifulSoup進行解析,獲取英雄列表:

hero_soup = bs(html,'lxml')
hero_html_list=hero_soup.find("ul",class_="herolist")
all_hero_list =hero_html_list.find_all("li")
print(all_hero_list[0].text)
print("https://"+all_hero_list[0].img["src"].strip("/"))

輸出:

夏洛特
https://game.gtimg.cn/images/yxzj/img201606/heroimg/536/536.jpg

顯然,獲取到了基本資訊。

進一步整合,獲取所有英雄名稱和圖片連結列表:

gen_heros=[[info.text, "https://"+info.img["src"].strip("/")] for info in all_hero_list]
gen_heros

輸出L:

[['夏洛特', 'https://game.gtimg.cn/images/yxzj/img201606/heroimg/536/536.jpg'],
 ['阿古朵', 'https://game.gtimg.cn/images/yxzj/img201606/heroimg/533/533.jpg'],
 ['蒙恬', 'https://game.gtimg.cn/images/yxzj/img201606/heroimg/527/527.jpg'],
 ['鏡', 'https://game.gtimg.cn/images/yxzj/img201606/heroimg/531/531.jpg'],
 ['蒙犽', 'https://game.gtimg.cn/images/yxzj/img201606/heroimg/524/524.jpg'],
...
 ['妲己', 'https://game.gtimg.cn/images/yxzj/img201606/heroimg/109/109.jpg'],
 ['墨子', 'https://game.gtimg.cn/images/yxzj/img201606/heroimg/108/108.jpg'],
 ['趙雲', 'https://game.gtimg.cn/images/yxzj/img201606/heroimg/107/107.jpg'],
 ['小喬', 'https://game.gtimg.cn/images/yxzj/img201606/heroimg/106/106.jpg'],
 ['廉頗', 'https://game.gtimg.cn/images/yxzj/img201606/heroimg/105/105.jpg']]

3.資料整合

現在需要將hero_list和gen_heros兩個列表中的資料進行整合,並且實現根據關鍵字檢索。

先實現給hero_list中的英雄定義英雄型別和根據英雄名在hero_list檢索是否存在的函數,如下:

def build_hero_type(hero):
    combine_type = []    
    if "hero_type" in hero:
        combine_type.append(hero_type[hero["hero_type"]])
    if "new_type" in hero:
        combine_type.append(hero_type[hero["new_type"]])
    if "hero_type2" in hero:
        combine_type.append(hero_type[hero["hero_type2"]])
    return(('|').join(combine_type))

def search_for_hero_info(name=None):
    for hero in hero_list:
        if "cname" in hero:
            if hero["cname"] == name:
                return hero
    return None

這兩個函數的簡單使用如下:

su_lie=search_for_hero_info("蘇烈")
print(su_lie)

hero_detail = search_for_hero_info(gen_heros[0][0])
print(hero_detail)

hero_detail["skin_name"].strip("&#10;'")
build_hero_type(hero_detail)

輸出如下:

{'ename': 194, 'cname': '蘇烈', 'title': '不屈鐵壁', 'pay_type': 10, 'new_type': 0, 'hero_type': 3, 'hero_type2': 1, 'skin_name': '不屈鐵壁|愛與和平|堅韌之力|玄武志'}
{'ename': 536, 'cname': '夏洛特', 'title': '玫瑰劍士', 'new_type': 1, 'hero_type': 1, 'skin_name': '玫瑰劍士'}

'戰士|戰士'

現實現合併兩個列表的函數:

def merge_hero_info(hero_html, hero_json):
    all_heros = []
    for hero in hero_html:
        hero_detail = search_for_hero_info(hero[0])
        all_heros.append([hero[0],build_hero_type(hero_detail),hero_detail.get("skin_name",'').strip("&#10;'"),hero[1]])
    return all_heros

使用該函數合併兩個列表如下:

combined_heros=[]
combined_heros = merge_hero_info(gen_heros, hero_list)
combined_heros[:5]

輸出:

[['夏洛特',
  '戰士|戰士',
  '玫瑰劍士',
  'https://game.gtimg.cn/images/yxzj/img201606/heroimg/536/536.jpg'],
 ['阿古朵',
  '坦克|全部',
  '山林之子',
  'https://game.gtimg.cn/images/yxzj/img201606/heroimg/533/533.jpg'],
 ['蒙恬',
  '戰士|全部',
  '秩序統將|秩序獵龍將',
  'https://game.gtimg.cn/images/yxzj/img201606/heroimg/527/527.jpg'],
 ['鏡',
  '刺客|全部',
  '破鏡之刃|冰刃幻境',
  'https://game.gtimg.cn/images/yxzj/img201606/heroimg/531/531.jpg'],
 ['蒙犽',
  '射手|全部',
  '烈炮小子|歸虛夢演',
  'https://game.gtimg.cn/images/yxzj/img201606/heroimg/524/524.jpg']]

4.建立索引

現在進一步實現建立索引實現快速查詢,實現根據英雄名、英雄型別、英雄面板。
要實現輸入一個keyword為蘇烈,可以返回下面這樣的結果:

['蘇烈',
  [['蘇烈',
    '坦克|戰士|戰士',
    '不屈鐵壁|愛與和平',
    'http://game.gtimg.cn/images/yxzj/img201606/heroimg/194/194.jpg']]],
 ['坦克',
  [['蘇烈',
    '坦克|戰士|戰士',
    '不屈鐵壁|愛與和平',
    'http://game.gtimg.cn/images/yxzj/img201606/heroimg/194/194.jpg'],
    ['鎧',
    '戰士|全部|坦克',
    '破滅刃鋒|龍域領主',
    'http://game.gtimg.cn/images/yxzj/img201606/heroimg/193/193.jpg']
    ]
    ]
  ]
]

先實現根據英雄資訊生成關鍵字列表:

# 根據英雄資訊,生成keyword的列表
def get_keywords_array(hero):
    keywords =[]
    if hero[0]:
        keywords.append(hero[0])
    if hero[1]:
        keywords += hero[1].split('|')
    if hero[2]:
        keywords +=hero[2].split('|')
    return keywords

get_keywords_array(combined_heros[12])

輸出:

['豬八戒', '坦克', '全部', '無憂猛士', '年年有餘']

再實現新增索引和建立搜尋列表的函數:

# 新增索引到搜尋資料列表中
def add_to_index(index, keyword, info):
    for entry in index:
        if entry[0] == keyword:
            entry[1].append(info)
            return
    #not find
    index.append([keyword,[info]])

# 建立搜尋資料列表    
def build_up_index(index_array):
    for hero_info in combined_heros:
        keywords = get_keywords_array(hero_info)
        for key in keywords:
            add_to_index(index_array,key,hero_info) 

最後實現根據關鍵字檢索資訊的函數:

# 根據關鍵詞在列表中搜尋
def lookup(index,keyword):
    for entry in index:
        if entry[0] == keyword:
            return entry[1] 
    #not find
    return entry[0]

檢索測試如下:

search_index=[]
build_up_index(search_index)
lookup(search_index,"刺客")

輸出:

[['鏡',
  '刺客|全部',
  '破鏡之刃|冰刃幻境',
  'https://game.gtimg.cn/images/yxzj/img201606/heroimg/531/531.jpg'],
 ['馬超',
  '戰士|全部|刺客',
  '',
  'https://game.gtimg.cn/images/yxzj/img201606/heroimg/518/518.jpg'],
 ['雲中君',
  '刺客|全部|戰士',
  '荷魯斯之眼',
  'https://game.gtimg.cn/images/yxzj/img201606/heroimg/506/506.jpg'],
 ['上官婉兒',
  '法師|全部|刺客',
  '驚鴻之筆|修竹墨客',
  'https://game.gtimg.cn/images/yxzj/img201606/heroimg/513/513.jpg'],
 ['司馬懿',
  '刺客|全部|法師',
  '寂滅之心|魘語軍師',
  'https://game.gtimg.cn/images/yxzj/img201606/heroimg/137/137.jpg'],
 ...
 ['韓信',
  '刺客|全部',
  '國士無雙|街頭霸王|教廷特使|白龍吟|逐夢之影',
  'https://game.gtimg.cn/images/yxzj/img201606/heroimg/150/150.jpg'],
 ['貂蟬',
  '法師|全部|刺客',
  '絕世舞姬|異域舞娘|聖誕戀歌|逐夢之音|仲夏夜之夢',
  'https://game.gtimg.cn/images/yxzj/img201606/heroimg/141/141.jpg'],
 ['李白',
  '刺客|全部',
  '青蓮劍仙|範海辛|千年之狐|鳳求凰|敏銳之力',
  'https://game.gtimg.cn/images/yxzj/img201606/heroimg/131/131.jpg'],
 ['阿軻',
  '刺客|全部',
  '信念之刃|愛心護理|暗夜貓娘|致命風華|節奏熱浪',
  'https://game.gtimg.cn/images/yxzj/img201606/heroimg/116/116.jpg'],
 ['趙雲',
  '戰士|全部|刺客',
  '蒼天翔龍|忍●炎影|未來紀元|皇家上將|嘻哈天王|白執事|引擎之心',
  'https://game.gtimg.cn/images/yxzj/img201606/heroimg/107/107.jpg']]

可以看到,在英雄名、英雄型別和面板為刺客的資料都被檢索出來。

此時再檢視建立索引後的資料結構:

display(len(search_index),search_index[4])

輸出:

446

['坦克',
 [['阿古朵',
   '坦克|全部',
   '山林之子',
   'https://game.gtimg.cn/images/yxzj/img201606/heroimg/533/533.jpg'],
  ['豬八戒',
   '坦克|全部',
   '無憂猛士|年年有餘',
   'https://game.gtimg.cn/images/yxzj/img201606/heroimg/511/511.jpg'],
  ['嫦娥',
   '法師|全部|坦克',
   '寒月公主|露花倒影',
   'https://game.gtimg.cn/images/yxzj/img201606/heroimg/515/515.jpg'],
  ['孫策',
   '坦克|全部|戰士',
   '光明之海|海之徵途|貓狗日記',
   'https://game.gtimg.cn/images/yxzj/img201606/heroimg/510/510.jpg'],
  ['夢奇',
   '坦克|全部',
   '入夢之靈|美夢成真|胖達榮榮',
   'https://game.gtimg.cn/images/yxzj/img201606/heroimg/198/198.jpg'],
 ...
  ['白起',
   '坦克|全部',
   '最終兵器|白色死神|猙|星夜王子',
   'https://game.gtimg.cn/images/yxzj/img201606/heroimg/120/120.jpg'],
  ['鍾無豔',
   '戰士|全部|坦克',
   '野蠻之錘|生化警戒|王者之錘|海灘麗影',
   'https://game.gtimg.cn/images/yxzj/img201606/heroimg/117/117.jpg'],
  ['劉禪',
   '輔助|全部|坦克',
   '暴走機關|英喵野望|紳士熊喵|天才門將',
   'https://game.gtimg.cn/images/yxzj/img201606/heroimg/114/114.jpg'],
  ['莊周',
   '輔助|全部|坦克',
   '逍遙幻夢|鯉魚之夢|蜃樓王|雲端築夢師',
   'https://game.gtimg.cn/images/yxzj/img201606/heroimg/113/113.jpg'],
  ['廉頗',
   '坦克|全部',
   '正義爆轟|地獄巖魂',
   'https://game.gtimg.cn/images/yxzj/img201606/heroimg/105/105.jpg']]]

可以看到,為所有關鍵字都建立了索引,因此長度比英雄數量要多得多。

總結

爬蟲是Python最廣泛的應用之一,可以從網頁中快速獲取大量資料。Python為我們提供了大量獲取網路資料、提取網路資料和處理網路資料的庫,如requests、selenium、BeautifulSoup、re、jieba、wordcloud等,合理靈活使用這些工具可以進行高效的爬蟲開發。