用Python做爬蟲

一、初步認識

  1. 如何入門 Python 爬蟲?大家可以看看知乎這篇文章(簡單易懂)
  2. 什么是爬蟲?大家可以先看一下下面幾張圖。





二、我的學習步驟:

1、Python開發環境搭建

下圖為基于Intellij IDEA的python環境搭建成功的界面,大家自行百度搭建過程。


2、Python正則表達式(建議觀看慕課網這個視頻

問:為什么要使用正則?
答:雖然字符串匹配可以實現,但是每一次匹配都要單獨完成,重復代碼多,我們能否把它做成一個規則?因此正則出現了。

正則表達式概念

  • 使用單個字符串來描述匹配一系列符合某個句法規則的字符串
  • 是對字符串操作的一種邏輯公式
  • 應用場景:處理文本和數據
  • 正則表達式過程:依次拿出表達式好文本中的字符比較,如果沒一個字符都能匹配,則匹配成功;否則匹配失敗。

正則表達式匹配流程

正則表達式元字符和語法

3、python開發簡單爬蟲(輕量級爬蟲、不需要登錄的靜態加載網頁抓?。?/h4>
  • URL管理器:管理待爬取URL集合和已抓取URL集合(防止重復抓取、防止循環抓取)


  • 網頁下載器:將互聯網上URL對應的網頁下載到本地的工具,有urllib2(python官方基礎模塊)和requests(第三方包更強大)

以下為urllib2網頁下載方法的三種方法示例:

# coding:utf-8
import urllib2, cookielib

url='http://www.baidu.com'

print"1)urllib2下載網頁方法1"
# 直接請求
response1 = urllib2.urlopen(url)
# 獲取狀態碼,如果是200表示獲取成功
print response1.getcode()
# 讀取網頁內容的長度
print len(response1.read())

print"2)urllib2下載網頁方法1"
# 創建Request對象
request = urllib2.Request(url)
# 添加數據
# request.add_data('a', '1')
# 添加http的header
request.add_header('User-Agent', 'Mozilla/5.0')
# 發送請求獲取結果
response2 = urllib2.urlopen(request)
print response2.getcode()
print len(response2.read())

print"3)添加特殊情景的處理器"
# 創建cookie容器
cj = cookielib.CookieJar()
# 創建1個opener
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
# 給urllib2安裝opener
urllib2.install_opener(opener)
# 使用帶有cookie的urllib2訪問網頁
response3 = urllib2.urlopen(url)
print response3.getcode()
print cj
print response3.read()
  • 網頁解析器


下面介紹一下beautifulsoup4(Python第三方庫,用于從HTML或XML中提取數據)
首先,安裝beautifulsoup4


測試代碼

測試成功

下面我們來看看Beautifulsoup的實例分析

# coding:utf-8
from bs4 import BeautifulSoup
import re

#舉例:解析網頁文檔字符串
html_doc = """
<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  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>
"""

# 創建Beautifulsoup對象
soup = BeautifulSoup(html_doc,               #HTML文檔字符
                     'html.parser',          #HTML解析器
                     from_encoding='utf-8')  #HTML文檔的編碼
#方法find_all(name,attrs,string) 節點名稱、節點屬性、節點內容
print '獲取所有的鏈接'
links=soup.find_all('a')
for link in links:
    print link.name,link['href'],link.get_text()

print '獲取Lacie的鏈接'
link_code = soup.find('a',)
print link_code.name,link_code['href'],link_code.get_text()

# bs4之強大:支持正則匹配
print'正則匹配'
link_node = soup.find('a',href=re.compile(r"ill"))
print link_node.name,link_node['href'],link_node.get_text()

print '獲取P段落文字'
p_node = soup.find('p',class_="title")  #class要加下劃線
print p_node.name,p_node.get_text()

4、實戰演練:爬取百度百科1000個頁面的數據

  • 確定目標:確定要抓取哪個網站的哪些網頁的哪些數據
  • 分析目標:制定抓取這些網站數據的策略(URL格式、數據格式、頁面編碼)[ 實際操作:打開網站,鎖定位置,右鍵審查元素]

以下是我在慕課網學習簡單爬蟲來爬取百度百科的總結:

  • 思路:一個url管理器,來獲取和管理所有需要爬取的鏈接a,比如在這里我們先獲取https://baike.baidu.com/item/Python這個頁面的所有a標簽,將其存入一個容器(new_urls)中,然后依次爬取這個容器中的所有url,每爬一次,把爬取過的url從這個容器中刪去,加入到old_urls容器中, 并且加入到new_urls時要判斷這個url是否在new_urls和old_urls已經存在,若存在,不加入,防止重復爬取。然后通過頁面下載器利用urllib2下載我們需要的頁面代碼,頁面解析器html_parse利用beautifulsoup獲取我們需要的數據。

    目錄展示

  • 具體代碼
    1.主函數 spider_main.py

# coding:utf-8
from baike_spider import url_manager, html_downloader, html_parser, html_outputer


class SpiderMain(object):
    def __init__(self):
        self.urls = url_manager.UrlManager()
        self.downloader = html_downloader.HtmlDownloader()
        self.parser = html_parser.HtmlParser()
        self.outputer = html_outputer.HtmlOutputer()

    def craw(self, root_url):
        count = 1
        self.urls.add_new_url(root_url)

        while self.urls.has_new_url():
            try:
                new_url = self.urls.get_new_url()
                print 'craw %d : %s' % (count, new_url)
                html_cont = self.downloader.download(new_url)
                new_urls, new_data = self.parser.parse(new_url, html_cont)
                self.urls.add_new_urls(new_urls)
                self.outputer.collect_data(new_data)

                if count == 1000:
                    break
                count = count + 1
            except:
                print 'craw failed'

        self.outputer.output_html()

if __name__ == "__main__":
    # 爬蟲入口頁面
    root_url = "https://baike.baidu.com/item/Python/407313"
    obj_spider = SpiderMain()
    # 啟動爬蟲
    obj_spider.craw(root_url)

2.url管理器 url_manager.py

# coding:utf-8

class UrlManager(object):
    def __init__(self):
        self.new_urls = set()
        self.old_urls = set()

    #向管理器中添加新的url
    def add_new_url(self, url):
        if url is None:
            return
        if url not in self.new_urls and url not in self.old_urls:
            self.new_urls.add(url)

    #向管理器中添加批量的url
    def add_new_urls(self, urls):
        if urls is None or len(urls) == 0:
            return
        for url in urls:
            self.add_new_url(url)

    #判斷管理器中是否有新的url
    def has_new_url(self):
        return len(self.new_urls) != 0

    #獲取新的帶爬取的url
    def get_new_url(self):
        new_url = self.new_urls.pop()  # 獲取并移除
        self.old_urls.add(new_url)
        return new_url

3.頁面下載器 html_downloader.py

# coding:utf-8
import urllib2

class HtmlDownloader(object):

    def download(self,url):
        if url is None:
            return None

        response=urllib2.urlopen(url) #因為百度百科比較簡單,所以只使用了urllib2這個模塊最簡單的方法

        if response.getcode() != 200:
            return None

        return response.read()

4.頁面解析器 html_parser.py

# coding:utf-8

from bs4 import BeautifulSoup
import re
import urlparse

class HtmlParser(object):
    def _get_new_urls(self, page_url, soup):
        new_urls = set()
        # /view/123.htm
        # links = soup.find_all('a', href=re.compile(r"/item/\d+\.html"))
        links = soup.find_all('a', href=re.compile(r"/item/(.*)"))
        for link in links:
            new_url = link['href']
            new_full_url = urlparse.urljoin(page_url, new_url)  # 拼接url
            new_urls.add(new_full_url)
        return new_urls

    def _get_new_data(self, page_url, soup):
        res_data = {}  # 字典

        # url
        res_data['url'] = page_url

        # <dd class="lemmaWgt-lemmaTitle-title"><h1>Python</h1>
        title_node = soup.find('dd', class_="lemmaWgt-lemmaTitle-title").find("h1")
        res_data['title'] = title_node.get_text()

        # <div class="lemma-summary" label-module="lemmaSummary">
        summary_node = soup.find('div', class_="lemma-summary")
        res_data['summary'] = summary_node.get_text()
        return res_data

    def parse(self, page_url, html_cont):
        if page_url is None or html_cont is None:
            return

        soup = BeautifulSoup(html_cont, 'html.parser', from_encoding='utf-8')
        new_urls = self._get_new_urls(page_url, soup)
        new_data = self._get_new_data(page_url, soup)
        return new_urls, new_data

4.頁面輸出器 html_outputer.py

# coding:utf-8

class HtmlOutputer(object):
    def     __init__(self):
        self.datas = []

    def collect_data(self, data):
        if data is None:
            return
        self.datas.append(data)

    def output_html(self):
        fout = open('output.html', "w")

        fout.write("<html>")
        fout.write("<body>")
        fout.write("<table>")

        # ascii
        for data in self.datas:
            fout.write("<tr>")
            fout.write("<td>%s</td>" % data['url'])
            fout.write("<td>%s</td>" % data['title'].encode('utf-8'))
            fout.write("<td>%s</td>" % data['summary'].encode('utf-8'))
            fout.write("</tr>")

        fout.write("</html>")
        fout.write("</body>")
        fout.write("</table>")

        fout.close()

運行后,會在目錄下產生output.html,這個文件即是爬取結果的展示。



成功爬取完1000個頁面的數據!

5、可以再深入理解一下爬蟲的意義了

大家可以觀看這篇大話爬蟲這篇文章!
然后就是知乎這篇文章如何入門python爬蟲

下面稍微做一下總結:

1、Python基礎準備(可以去看廖雪峰老師的教程,2.7的。至少這些功能和語法你要有基本的掌握 )

  • list,dict:用來序列化你爬的東西
  • 切片:用來對爬取的內容進行分割,生成
  • 條件判斷(if等):用來解決爬蟲過程中哪些要哪些不要的問題
  • 循環和迭代(for while ):用來循環,重復爬蟲動作
  • 文件讀寫操作:用來讀取參數、保存爬下來的內容等

2、網頁基本知識

  • 基本的HTML語言知識(知道href等大學計算機一級內容即可)
  • 理解網站的發包和收包的概念(POST GET)
  • 稍微一點點的js知識,用于理解動態網頁(當然如果本身就懂當然更好啦)

3、分析語言

  • NO.1 正則表達式:扛把子技術,總得會最基礎的
  • NO.2 XPATH:高效的分析語言,表達清晰簡單,掌握了以后基本可以不用正則
    參考:XPath 教程
  • NO.3 Beautifulsoup:
    美麗湯模塊解析網頁神器,一款神器,如果不用一些爬蟲框架(如后文講到的scrapy),配合request,urllib等模塊(后面會詳細講),可以編寫各種小巧精干的爬蟲腳本
    官網文檔:Beautiful Soup 4.2.0 文檔

4、高效工具輔助

  • NO.1 F12 開發者工具:
    看源代碼:快速定位元素
    分析xpath:1、此處建議谷歌系瀏覽器,可以在源碼界面直接右鍵看
  • NO.2 抓包工具:
    推薦httpfox,火狐瀏覽器下的插件,比谷歌火狐系自帶的F12工具都要好,可以方便查看網站收包發包的信息
  • NO.3 XPATH CHECKER (火狐插件):
    非常不錯的xpath測試工具,但是有幾個坑,都是個人踩過的,在此告誡大家:
    1、xpath checker生成的是絕對路徑,遇到一些動態生成的圖標(常見的有列表翻頁按鈕等),飄忽不定的絕對路徑很有可能造成錯誤,所以這里建議在真正分析的時候,只是作為參考
    2、記得把如下圖xpath框里的“x:”去掉,貌似這個是早期版本xpath的語法,目前已經和一些模塊不兼容(比如scrapy),還是刪去避免報錯
  • NO.4 正則表達測試工具:
    在線正則表達式測試 ,拿來多練練手,也輔助分析!里面有很多現成的正則表達式可以用,也可以進行參考!

5、更多

  • 模塊:python的火,很大原因就是各種好用的模塊,這些模塊是居家旅行爬網站常備的。
    urllib
    urllib2
    requests
  • 框架:不想重復造輪子,有沒有現成的框架?
    華麗麗的scrapy
  • 遇到動態頁面怎么辦?
    selenium(會了這個配合scrapy無往不利,是居家旅行爬網站又一神器,下一版更新的時候會著重安利,因為這塊貌似目前網上的教程還很少)
    phantomJS(不顯示網頁的selenium)
  • 遇到反爬蟲策略驗證碼之類咋整?
    PIL
    opencv
    pybrain
    打碼平臺
  • 數據庫:這里我認為開始并不需要非常深入,在需要的時候再學習即可。
    mysql
    mongodb
    sqllite
  • 爬來的東西怎么用?
    numpy 數據分析,類似matlab的模塊
    pandas(基于numpy的數據分析模塊,相信我,如果你不是專門搞TB級數據的,這個就夠了)
  • 進階技術:
    多線程
    分布式

三、后期思考與改進

1)效率
如果你直接加工一下上面的代碼直接運行的話,你需要一整年才能爬下整個豆瓣的內容。更別說Google這樣的搜索引擎需要爬下全網的內容了。

問題出在哪呢?需要爬的網頁實在太多太多了,而上面的代碼太慢太慢了。設想全網有N個網站,那么分析一下判重的復雜度就是N*log(N),因為所有網頁要遍歷一次,而每次判重用set的話需要log(N)的復雜度。OK,OK,我知道python的set實現是hash——不過這樣還是太慢了,至少內存使用效率不高。

通常的判重做法是怎樣呢?Bloom Filter. 簡單講它仍然是一種hash的方法,但是它的特點是,它可以使用固定的內存(不隨url的數量而增長)以O(1)的效率判定url是否已經在set中??上煜聸]有白吃的午餐,它的唯一問題在于,如果這個url不在set中,BF可以100%確定這個url沒有看過。但是如果這個url在set中,它會告訴你:這個url應該已經出現過,不過我有2%的不確定性。注意這里的不確定性在你分配的內存足夠大的時候,可以變得很小很少。一個簡單的教程:Bloom Filters by Example
注意到這個特點,url如果被看過,那么可能以小概率重復看一看(沒關系,多看看不會累死)。但是如果沒被看過,一定會被看一下(這個很重要,不然我們就要漏掉一些網頁了?。?。 [IMPORTANT: 此段有問題,請暫時略過]
好,現在已經接近處理判重最快的方法了。另外一個瓶頸——你只有一臺機器。不管你的帶寬有多大,只要你的機器下載網頁的速度是瓶頸的話,那么你只有加快這個速度。用一臺機子不夠的話——用很多臺吧!當然,我們假設每臺機子都已經進了最大的效率——使用多線程(python的話,多進程吧)。

2)集群化抓取
爬取豆瓣的時候,我(這不是我hh)總共用了100多臺機器晝夜不停地運行了一個月。想象如果只用一臺機子你就得運行100個月了...
那么,假設你現在有100臺機器可以用,怎么用python實現一個分布式的爬取算法呢?
我們把這100臺中的99臺運算能力較小的機器叫作slave,另外一臺較大的機器叫作master,那么回顧上面代碼中的url_queue,如果我們能把這個queue放到這臺master機器上,所有的slave都可以通過網絡跟master聯通,每當一個slave完成下載一個網頁,就向master請求一個新的網頁來抓取。而每次slave新抓到一個網頁,就把這個網頁上所有的鏈接送到master的queue里去。同樣,bloom filter也放到master上,但是現在master只發送確定沒有被訪問過的url給slave。Bloom Filter放到master的內存里,而被訪問過的url放到運行在master上的Redis里,這樣保證所有操作都是O(1)。(至少平攤是O(1),Redis的訪問效率見:LINSERT – Redis)

3)后續處理

  • 有效地存儲(數據庫應該怎樣安排)
  • 有效地判重(這里指網頁判重,咱可不想把人民日報和抄襲它的大民日報都爬一遍)
  • 有效地信息抽取(比如怎么樣抽取出網頁上所有的地址抽取出來,“朝陽區奮進路-
    中華道”),搜索引擎通常不需要存儲所有的信息,比如圖片我存來干嘛...
  • 及時更新(預測這個網頁多久會更新一次)
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,117評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,860評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 177,128評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,291評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,025評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,421評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,477評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,642評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,177評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,970評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,157評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,717評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,410評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,821評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,053評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,896評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,157評論 2 375

推薦閱讀更多精彩內容