咨詢公司眼中的中國(一)

前言

與其說這是對咨詢公司觀點的總結(jié)绽左,不如說這是一篇爬蟲技術(shù)和文本挖掘技術(shù)的展示。我們試圖抓取數(shù)家咨詢公司發(fā)布關(guān)于中國的報告,并使用文本挖掘技術(shù)分析其觀點凡资。我們不僅將盡可能詳細(xì)地解釋本文使用的代碼渴肉,還將講解寫作代碼的過程冗懦。我們不假設(shè)讀者有python的經(jīng)驗,但如果有一定的編程經(jīng)驗會很有幫助宾娜。本文不講解python的安裝過程批狐。

本文是如何運(yùn)行Python程序的

如果您不是程序員,那么您的編程經(jīng)驗或許是這樣:打開一個軟件前塔,比如MATLAB或者RStudio嚣艇,在某個用于編輯代碼窗口輸入代碼,然后點擊運(yùn)行按鈕华弓,最后查看某個結(jié)果窗口食零。這樣的軟件通常被稱作IDE (integrated development environment)。Python也有對應(yīng)的IDE寂屏,但我們不建議使用贰谣。相反娜搂,我們建議使用Notepad++來寫程序。一個簡單的程序如下:

  1. 在C盤建立一個名為test.py的文件
  2. Notepad++打開該文件吱抚,插入如下代碼
  3. 打開cmd百宇,移動到當(dāng)前地址,例如此處則輸入cd C:\Work
  4. 輸入python test.py秘豹,即得到如下結(jié)果
Paste_Image.png

獲取研究報告

我們認(rèn)為盡量準(zhǔn)確的携御、不神秘的用語總是有益的。當(dāng)我們說“抓取報告”時既绕,我們希望做的事情大致如下:

  1. 打開一篇報告所在的網(wǎng)頁
  2. 打開該網(wǎng)頁的html源代碼
  3. 觀察網(wǎng)頁結(jié)構(gòu)啄刹,找出自己想要的內(nèi)容的位置
  4. 抓取想要的內(nèi)容
  5. 打開下一個網(wǎng)頁

沒有什么比例子更好的講解了。讓我們看如下例子凄贩。

獲取麥肯錫公司的報告

在開始前誓军,我們在腳本的最頂端加入如下代碼:

import io 
import sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer,encoding='utf8')

這三行代碼與抓取文章無關(guān),這是為了保證cmd能夠顯示某些特定字符疲扎。
下面我們打開一篇報告的網(wǎng)址:http://www.mckinseychina.com/who-is-winning-the-war-for-talent-in-china/
其內(nèi)容如下


我們感興趣的內(nèi)容有文章的標(biāo)題昵时,發(fā)布時間和內(nèi)容本身。現(xiàn)在我們開始講解抓取的過程评肆。

from urllib.request import urlopen
from bs4 import BeautifulSoup
import re

這三行代碼是為了加載我們所需要的工具债查。urlopen的作用是打開一個網(wǎng)頁,而BeautifulSoup則是一個非常強(qiáng)大的提取html信息的工具瓜挽。我們也需要re來處理正則表達(dá)式(后文將介紹)盹廷。我們將在下文看到它們的用法。
值得一提的是久橙,python有兩種加載的方式俄占。下文可以看出,使用from ... import時淆衷,我們可以直接使用函數(shù)缸榄,例如html = urlopen(url)。使用import 我們則需要加上包的名稱祝拯,例如x = re.compile("...")
本文代碼的第一個函數(shù)getMcKArticle(url)的作用是輸入一篇文章的網(wǎng)址后甚带,提取這篇文章的標(biāo)題、發(fā)布時間佳头、簡介和內(nèi)容鹰贵,并且存儲在一個txt文檔中。

html = urlopen(url)
soup = BeautifulSoup(html.read())

我們使用urlopen讀取文章的網(wǎng)址康嘉,將結(jié)果儲存在html這一變量中碉输。這時html變量就是網(wǎng)頁的源代碼。之后我們再使用BeautifulSoup亭珍,為提取信息做準(zhǔn)備敷钾。
讓我們打開網(wǎng)頁的源代碼枝哄,如下圖所示。

Paste_Image.png

我們想要的信息便藏在這混沌中阻荒。對于這混沌的html挠锥,我們只需要,既其大部分內(nèi)容都符合如下形式

<tag attribute="value">*some stuff here*</tag>

這里侨赡,tag是標(biāo)簽的名稱瘪贱,attribute是標(biāo)簽的屬性,some stuff here是標(biāo)簽內(nèi)容辆毡。
我們可以嘗試ctrl+f搜索類似time, date, description, content等來看看有沒有標(biāo)簽包含了我們想要的信息,經(jīng)過幾次嘗試甜害,我們可以找到如下內(nèi)容:

<meta property="og:title" content="Who is Winning the War for Talent........此處省略" />
<meta property="og:description" content="Despite the trend toward automation, job........此處省略." />
<meta property="article:published_time" content="2016-02-15T13:06:42+00:00" />
<div class="post-content"><p>While much has been made of China’........此處省略.......

以上便是我們想要的內(nèi)容舶掖。要抓取上文的內(nèi)容,我們需要先學(xué)習(xí)一下BeautifulSoup的一些功能尔店。一個例子勝過千言萬語眨攘。以下例子來自BeautifulSoup的官方文檔。

<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><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  class="sister" id="link2">Lacie</a> and
<a  class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p>

我們將以上html存儲在一個叫soupBeautifulSoup對象中嚣州,那么:

soup.title 輸出 
       <title>The Dormouse's story</title>
soup.findAll('a') 輸出 
      [<a class="sister"  id="link1">Elsie</a>,
       <a class="sister"  id="link2">Lacie</a>,
       <a class="sister"  id="link3">Tillie</a>]
soup.find(id="link3") 或者 soup.find({"id": "link3"}) 輸出 
       <a class="sister"  id="link3">Tillie</a>

大致來說鲫售,findAll輸出所有符合條件的結(jié)果。find輸出符合條件的第一個結(jié)果该肴。這兩個函數(shù)既可以用來按標(biāo)簽名稱查找情竹,也可以按標(biāo)簽屬性查找。請讀者自行體會匀哄。
于是秦效,我們使用

try:
    articleTitle = soup.find("meta",{'property':"og:title"})['content'].strip()
except:
    articleTitle = "This article has no title"

來提取文章的標(biāo)題。['content']的作用是提取出content這一屬性涎嚼。.strip()用于將結(jié)果左右的空格刪除阱州。注意我們使用了try: ... except:...,這是為了防止有的文章不能用這樣的方式找到標(biāo)題導(dǎo)致程序報錯法梯,這樣處理后苔货,如果沒有標(biāo)題,那么我們便指出它沒有標(biāo)題立哑。文章的發(fā)布時間和描述同理夜惭。
對于文章的正文,我們有

try:
    TarticlePara = soup.find("div", {"class":"post-content"}).findAll({"p","h1","h2","h3","h4","h5","h6"})
except:
    return None

我們觀察到刁憋,文章的正文在<div class="post-content">這一大標(biāo)簽的<p>, <h2>, <h3> 小標(biāo)簽下滥嘴,為了保險起見,我們將<p>,<h1>,...<h6>全部都找出來至耻。這樣我們就獲取了文章的正文了若皱。注意镊叁,有的文章不能靠這種方法找到正文,但這樣的文章我們就選擇忽略了走触,于是我們使用return none來跳出函數(shù)剩下的部分晦譬。
本函數(shù)剩余部分的作用是把結(jié)果輸出成為一個txt文件。我們用

with open('C:/Work/Scrapers/ConsultingReports/McKinsey/'+articleTitle[0:30]+'.txt','w+', encoding="utf8") as file:

來創(chuàng)建一個標(biāo)題是文章標(biāo)題的前30個字符的txt文件互广。

file.write('ThisIsTile:'+articleTitle+'\n')
file.write('ThisIsDate:'+articleTime+'\n')
file.write('ThisIsDesc:'+articleDescription+'\n\n')
for para in TarticlePara:
    file.write(para.get_text())

我們分別在文件中寫入文章的標(biāo)題敛腌、時間和描述。\n的作用是換到下一行惫皱。之后我們寫入文章的正文像樊。為什么我們這里要寫一個for循環(huán)呢?因為和標(biāo)題時間描述不同旅敷,文章的正文是按段落存儲的生棍,TarticlePara變量是這些段落的集合,所以不能直接寫入到文件里媳谁,我們只能逐個段落地寫入涂滴。
上文函數(shù)的作用是在我們找到一篇文章的網(wǎng)址后,獲取該文章的信息晴音。我們需要第二個函數(shù)柔纵,用以找到這些文章的網(wǎng)址,這便是getArticleLinks(motherUrl)锤躁。我們將一個起始網(wǎng)址輸入到該函數(shù)中搁料,輸出文章網(wǎng)址的集合。這個函數(shù)的思路如下:
我們先打開一個起始網(wǎng)頁(mother)系羞,提取出該網(wǎng)頁包含的所有鏈接加缘,選取那些指向文章的鏈接并存儲起來。之后我們跳到一篇文章的網(wǎng)頁(child 1)觉啊,選取文章鏈接并存儲拣宏。然后跳到下一篇文章(child 2),以此類推杠人。
我們需要注意到一個問題勋乾,即我們不可避免的會遇到很多重復(fù)的文章鏈接,怎么保證我們最后輸出的文章網(wǎng)址集合沒有重復(fù)呢嗡善?所幸的是辑莫,pythonset()可以實現(xiàn)這一功能。當(dāng)我們向一個set輸入重復(fù)的值時罩引,它只會保留一個記錄各吨。我們將獲取的所有文章鏈接都輸入到一個set后,最后的結(jié)果就是沒有重復(fù)的文章鏈接集合了袁铐。
我們先使用BeautifulSoup來存儲起始網(wǎng)址揭蜒,然后調(diào)用一個全局變量articlePages横浑。我們的目的是將所有文章鏈接都存儲在這個變量里,而且我們需要重復(fù)地使用這個函數(shù)屉更。如果我們選擇在這個函數(shù)的內(nèi)部建立一個新變量并存儲網(wǎng)址的話徙融,那么每次重新調(diào)用這個函數(shù)時,這個變量便會被清空瑰谜,這顯然不是我們想要的欺冀。所以我們在函數(shù)外部創(chuàng)建一個集合articlePages = set(),然后再在函數(shù)中調(diào)用萨脑。
接下來我們提取所有文章的網(wǎng)址隐轩。觀察源代碼,我們發(fā)現(xiàn)包含網(wǎng)址的標(biāo)簽結(jié)構(gòu)如下:

<a style="display:inline-block;" class="icon link-icon" href="[http://www.mckinseychina.com/how-china-count....此處省略]">

標(biāo)簽名是a渤早,而我們想要的信息存儲在href這一屬性中龙助。我們運(yùn)用findAll函數(shù)來提取所有的網(wǎng)址。注意到這個令人生畏的怪物:

href=re.compile("http://www.mckinseychina.com/([A-Za-z0-9]+-){3,}[A-Za-z0-9]+/")

這里我們使用了正則表達(dá)式蛛芥。我們提出一種模式,然后搜尋能夠匹配該模式的字符串军援。因篇幅所限仅淑,我們沒有辦法對正則表達(dá)式做一個哪怕淺顯的介紹,但我們可以仔細(xì)看看這里這個例子胸哥。
首先我們觀察文章網(wǎng)址都有什么共同點涯竟,請看這三個網(wǎng)址:

http://www.mckinseychina.com/who-is-winning-the-war-for-talent-in-china/
http://www.mckinseychina.com/what-might-happen-in-china-in-2016/
http://www.mckinseychina.com/which-china-headline-do-you-prefer/

我們發(fā)現(xiàn),這些網(wǎng)址的共同點如下:

  1. 它們都以http://www.mckinseychina.com作為開頭
  2. 它們后接的都是文章的標(biāo)題空厌,每一個單詞或數(shù)字用-隔開

現(xiàn)在我們可以開始寫正則表達(dá)式來匹配了這一模式了

  1. http://www.mckinseychina.com/: 到這里庐船,我們規(guī)定網(wǎng)址的開頭
  2. ([A-Za-z0-9]+-): [A-Za-z0-9]的作用是,匹配任意一個大寫字母嘲更、小寫字母或者數(shù)字筐钟。我們在后邊跟上+,則表示我們匹配任意次數(shù)赋朦。之后我們跟上-篓冲,則表示我們匹配鏈接符號-。我們用(...)表示這是一個整體宠哄。
  3. {3,}: 這表示我們匹配上一步中的部分3次或以上壹将。為什么要這樣規(guī)定呢?因為觀察網(wǎng)頁毛嫉,我們發(fā)現(xiàn)诽俯,有這樣的網(wǎng)址
http://www.mckinseychina.com/contact-us

以上網(wǎng)頁顯示的是聯(lián)系信息,而這顯然不是我們想要的承粤。而這樣的網(wǎng)址都很短暴区,因此我們匹配3次以上用以忽略這樣的網(wǎng)址闯团。

  1. [A-Za-z0-9]+/: 網(wǎng)址的最后以單詞或數(shù)字加上/結(jié)束,并且沒有-颜启,因此我們?nèi)绱瞬僮鳌?br> 很顯然偷俭,以上僅僅是一種匹配的方法,讀者大可以自行觀察其它的匹配方法缰盏。
    細(xì)心的讀者可能已經(jīng)發(fā)現(xiàn)了涌萤,我們使用了一種很取巧的方法,即我們只在文章之間跳轉(zhuǎn)口猜。我們沒有進(jìn)入類似于
http://www.mckinseychina.com/insights
http://www.mckinseychina.com/insights/innovation/

這類的網(wǎng)頁负溪。讀者在掌握了本文的技巧后,可以自行修改代碼以讓我們的文章爬取更完善济炎。
值得一提的是川抡,正則表達(dá)式是一個很反人類的工具,很容易出錯须尚,一個很好的寫正則表達(dá)式的輔助工具是http://regexpal.isbadguy.com/崖堤。
接下來,我們開始抓取和存儲這些鏈接了耐床。

for link in links:
        newArticlePage = link.attrs['href']
        articlePages.add(newArticlePage)
        print(str(len(articlePages)) + " preys slayed")

我們提取出這些網(wǎng)址密幔,用.add()方法來把這些網(wǎng)址加入到articlePages這個集合里。我們用print(str(len(articlePages)) + " preys slayed")來顯示集合里已經(jīng)存儲了多少篇文章撩轰,用以觀察進(jìn)度胯甩。

if len(articlePages)>=20:
        print("Hunting complete")
else:
        getArticleLinks(newArticlePage)
return articlePages

如果我們找到了20個以上的文章就滿足了,如果還沒有達(dá)到這個數(shù)字堪嫂,我們就選擇該網(wǎng)頁中最后一篇文章作為新的起始網(wǎng)頁偎箫,重復(fù)以上行為。最后我們輸出文章集合皆串。
有了上述兩個函數(shù)淹办,我們可以真正開始抓取文章了。

motherUrl = "http://www.mckinseychina.com"
articlePages = set()
articlePages = getArticleLinks(motherUrl)
summonCounter = 1
for page in list(articlePages):
        getMcKArticle(page)
        print(str(summonCounter) + ' out of ' + str(len(articlePages)) + " nightmares slayed")
        summonCounter += 1
print("Farewell good hunter. May you find worth in the waking world")
  1. 選擇起始網(wǎng)頁
  2. 創(chuàng)建文章集合
  3. getArticleLinks()填充文章集合
  4. 對文章集合中的每個網(wǎng)址恶复,用getMcKArticle()下載其內(nèi)容
    注意我們用了summonCounter來監(jiān)督抓取進(jìn)度娇唯。
    這樣我們就完成了文章的下載。print一句您喜歡的話來表揚(yáng)下自己吧寂玲!

附錄

獲取麥肯錫公司報告的完整代碼

import io 
import sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer,encoding='utf8')

from urllib.request import urlopen
from bs4 import BeautifulSoup
import re

def getMcKArticle(url):
    html = urlopen(url)
    soup = BeautifulSoup(html.read())
    
    try:
        articleTitle = soup.find("meta",{'property':"og:title"})['content'].strip()
    except:
        articleTitle = "This article has no title"
    
    try:
        articleTime = soup.find("meta", {"property":"article:published_time"})['content'].strip()
    except: 
        articleTime = "This article has no date"
    
    try:
        articleDescription = soup.find("meta",{'property':"og:description"})['content'].strip()
    except: 
        articleDescription = "This article has no description"
    
    try:
        TarticlePara = soup.find("div", {"class":"post-content"}).findAll({"p","h1","h2","h3","h4","h5","h6"})
    except:
        return None
    
    with open('C:/Work/Scrapers/ConsultingReports/McKinsey/'+articleTitle[0:30]+'.txt','w+', encoding="utf8") as file:
        file.write('ThisIsTile:'+articleTitle+'\n')
        file.write('ThisIsDate:'+articleTime+'\n')
        file.write('ThisIsDesc:'+articleDescription+'\n\n')
        for para in TarticlePara:
            file.write(para.get_text())

        
def getArticleLinks(motherUrl):
    html = urlopen(motherUrl)
    soup = BeautifulSoup(html)
    global articlePages
    
    links = soup.findAll("a", href=re.compile("http://www.mckinseychina.com/*([A-Za-z0-9]+-){3,}[A-Za-z0-9]+/"))

    for link in links:
        newArticlePage = link.attrs['href']
        articlePages.add(newArticlePage)
        print(str(len(articlePages)) + " preys slayed")
        
    if len(articlePages)>=20:
        print("Hunting complete")
    else:
        getArticleLinks(newArticlePage)

    return articlePages



motherUrl = "http://www.mckinseychina.com"
articlePages = set()
articlePages = getArticleLinks(motherUrl)
summonCounter = 1

for page in list(articlePages):
    getMcKArticle(page)
    print(str(summonCounter) + ' out of ' + str(len(articlePages)) + " nightmares slayed")
    summonCounter += 1
print("Farewell good hunter. May you find worth in the waking world")
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末塔插,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子拓哟,更是在濱河造成了極大的恐慌想许,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,599評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異流纹,居然都是意外死亡糜烹,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,629評論 3 385
  • 文/潘曉璐 我一進(jìn)店門漱凝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來疮蹦,“玉大人,你說我怎么就攤上這事茸炒°岛酰” “怎么了?”我有些...
    開封第一講書人閱讀 158,084評論 0 348
  • 文/不壞的土叔 我叫張陵壁公,是天一觀的道長感论。 經(jīng)常有香客問我,道長紊册,這世上最難降的妖魔是什么比肄? 我笑而不...
    開封第一講書人閱讀 56,708評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮囊陡,結(jié)果婚禮上芳绩,老公的妹妹穿的比我還像新娘。我一直安慰自己撞反,他們只是感情好妥色,可當(dāng)我...
    茶點故事閱讀 65,813評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著痢畜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鳍侣。 梳的紋絲不亂的頭發(fā)上丁稀,一...
    開封第一講書人閱讀 50,021評論 1 291
  • 那天,我揣著相機(jī)與錄音倚聚,去河邊找鬼线衫。 笑死,一個胖子當(dāng)著我的面吹牛惑折,可吹牛的內(nèi)容都是我干的授账。 我是一名探鬼主播,決...
    沈念sama閱讀 39,120評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼惨驶,長吁一口氣:“原來是場噩夢啊……” “哼白热!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起粗卜,我...
    開封第一講書人閱讀 37,866評論 0 268
  • 序言:老撾萬榮一對情侶失蹤屋确,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體攻臀,經(jīng)...
    沈念sama閱讀 44,308評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡焕数,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,633評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了刨啸。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片堡赔。...
    茶點故事閱讀 38,768評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖设联,靈堂內(nèi)的尸體忽然破棺而出善已,到底是詐尸還是另有隱情,我是刑警寧澤仑荐,帶...
    沈念sama閱讀 34,461評論 4 333
  • 正文 年R本政府宣布雕拼,位于F島的核電站,受9級特大地震影響粘招,放射性物質(zhì)發(fā)生泄漏啥寇。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,094評論 3 317
  • 文/蒙蒙 一洒扎、第九天 我趴在偏房一處隱蔽的房頂上張望辑甜。 院中可真熱鬧,春花似錦袍冷、人聲如沸磷醋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,850評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽邓线。三九已至,卻和暖如春煌恢,著一層夾襖步出監(jiān)牢的瞬間骇陈,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,082評論 1 267
  • 我被黑心中介騙來泰國打工瑰抵, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留你雌,地道東北人。 一個月前我還...
    沈念sama閱讀 46,571評論 2 362
  • 正文 我出身青樓二汛,卻偏偏與公主長得像婿崭,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子肴颊,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,666評論 2 350

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,855評論 25 707
  • 你每天重復(fù)著同樣的生活氓栈,做著同一件事,如同在一個小格子里婿着,從來不會發(fā)現(xiàn)格子之外的魅力颤绕。上學(xué)時幸海,同樣是三點一線。生活...
    大梳閱讀 284評論 0 0
  • 西豐大人思密達(dá)奥务,自上上個月從原同事那聽說你在沈陽因“費(fèi)用”問題突然被總部調(diào)回后就一直沒有你的消息了物独。是的,我早刪了...
    德嘉閱讀 188評論 1 3
  • 限于事務(wù)性的麻煩令人不悅氯葬,工作在探索中也許抓狂挡篓,工作在細(xì)致繁瑣的事務(wù)性工作中會厭倦,兩者之間也會會給你一個只是想工...
    Juliuslog閱讀 290評論 0 0
  • 如果你是個高富帥,那么會人會問有多高闯睹,有多富戏羽,有多帥,這樣分層次的話楼吃,因為高富帥也是有層次之分始花,而層次之分,也決定...
    羽商三少閱讀 593評論 9 3