Python 神兵譜之數據分析-上篇:數據采集

前言

刀槍劍戟歼培,斧鉞鉤叉,镋鐮槊棒茸塞,鞭锏錘抓躲庄。

神兵在手,妖魔不怕钾虐,劈荊斬棘噪窘,濺血生花。

行走江湖禾唁,誰沒有件趁手的兵器效览。

但是,兵器有帶楞的荡短,有帶刃兒的丐枉,有帶戎繩的,有帶鎖鏈兒的掘托,五花八門瘦锹,對于新手來說,真的是“亂花漸欲迷人眼”闪盔。

不過弯院,古有江湖百曉生,今有 Python 百媚生泪掀。百曉生所著的《兵器譜》讓江湖血雨腥風听绳,這百媚生也編纂了一部 Python 《神兵譜》,不知能否讓 Python 江湖掀起什么暴雨狂風异赫?

我們今天就來講講這《神兵譜》的“數據分析”篇椅挣。這“數據分析”篇又分為上、中塔拳、下三篇鼠证,分別針對數據分析的數據采集、數據處理及數據可視化三個方面靠抑。

本文不光是神兵的展示量九,更要教會大家簡單的使用,能夠幫助大家挑選合適趁手的兵器,才能在刀光劍影的江湖荠列,立于不敗之地类浪。

話不多說,直入主題弯予。

上篇:數據采集

說到數據采集戚宦,那最大名鼎鼎的方式就是“爬蟲”啦个曙,讓我們來看看百媚生帶給我們的“爬蟲”利器吧锈嫩,是不是真如傳言的“見血封喉”呢?

Requests

啥垦搬?為什么 requests 是“爬蟲”呼寸?

可不要小瞧了它!雖說 requests 是網絡請求庫猴贰,但它卻如高手手中的「木劍」一般对雪,用好了,一樣招招致命米绕。

使用 requests 發(fā)起攻擊(請求)瑟捣,猶如疾風般迅速,猶如落葉般輕盈栅干。

>>> r = requests.get('https://api.github.com/user', auth=('user', 'pass'))
>>> r.status_code
200
>>> r.headers['content-type']
'application/json; charset=utf8'
>>> r.encoding
'utf-8'
>>> r.text
'{"type":"User"...'
>>> r.json()
{'private_gists': 419, 'total_private_repos': 77, ...}

這就完了迈套?

如果對方是返回 Json 格式的 API 服務,是的碱鳞,這就完了桑李。我們已經拿到數據了。

如果對方是返回 XML 格式的 API 服務窿给,那么贵白,我們再搭配上原生的 xml 或者 lxml 解析器,滅敵于百步之外崩泡。

"""
content 是 xml 格式的字符串禁荒,即 r.text
例如
<?xml version="1.0"?>
<data>
    <country name="a"></country>
    <country name="b"></country>
    <country name="c"></country>
</data>
"""
import xml.etree.ElementTree as ET

tree = ET.parse(content)
root = tree.getroot()
# 遍歷節(jié)點
for child in root:
    print(child.tag, child.attrib)

lxml 更快更兇殘。

from lxml import etree

root = etree.XML(content)
for element in root.iter():
    print("%s - %s" % (element.tag, element.text))

lxml 更是支持強大的 xpathxlst 語法(語法文檔詳見參考)角撞。

# 使用 xpath 語法快速定位節(jié)點呛伴,提取數據
r = root.xpath('country')
text = root.xpath('country/text()')

xlst 進行快速轉換。

xslt_root = etree.XML('''\
    <xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="/">
        <foo><xsl:value-of select="/a/b/text()" /></foo>
    </xsl:template>
    </xsl:stylesheet>''')
transform = etree.XSLT(xslt_root)
f = StringIO('<a><b>Text</b></a>')
doc = etree.parse(f)
result_tree = transform(doc)

對手更兇殘了靴寂,是 HTML 文檔磷蜀!這下就需要 BeautifulSouplxml 解析器出馬了。

BeautifulSoup 雖然速度不快百炬,好在利于理解褐隆。

from bs4 import BeautifulSoup

# content 即 html 字符串, requests 返回的文本 text
soup = BeautifulSoup(content, 'html.parser')

print(soup.title)
print(soup.title.name)
print(soup.find_all('a'))
print(soup.find(id="link3"))
for link in soup.find_all('a'):
    print(link.get('href'))

上房揭瓦(解析網頁)剖踊,那是手到擒來庶弃。

而用 lxml 還是那么干凈利落衫贬。

html = etree.HTML(content)
result = etree.tostring(html, pretty_print=True, method="html")
print(result)
# 接下來就是 xpath 的表演時間

可見,木劍雖樸實歇攻,在高手手中固惯,也能變化無窮。如果是“接骨木”缴守,那更是了不得葬毫。最快速便捷的數據采集神兵,非 requests 莫屬屡穗!

Scrapy

接下來讓我們看看數據采集的百變神兵 —— Scrapy贴捡,分分鐘讓我們全副武裝。

# 創(chuàng)建一個項目
scrapy startproject tutorial
cd tutorial
# 創(chuàng)建一個爬蟲
scrapy genspider quotes quotes.toscrape.com

然后編輯項目下 spiders/quotes.py 爬蟲文件村砂。

import scrapy

class QuotesSpider(scrapy.Spider):
    name = "quotes"

    def start_requests(self):
        """
        生成初始請求烂斋。
        """
        urls = [
            'http://quotes.toscrape.com/page/1/',
            'http://quotes.toscrape.com/page/2/',
        ]
        for url in urls:
            yield scrapy.Request(url=url, callback=self.parse)

    def parse(self, response):
        """
        處理請求返回的響應。
        """
        page = response.url.split("/")[-2]
        filename = 'quotes-%s.html' % page
        with open(filename, 'wb') as f:
            f.write(response.body)
        self.log('Saved file %s' % filename)

然后就是啟動爬蟲础废。

scrapy crawl quotes

這還沒有發(fā)揮 Scrapy 的能力呢汛骂!

解析網頁

# CSS 解析
response.css('title::text').getall()
# xpath 解析
response.css('//title/text()').getall()

自動生成結果文件

import scrapy

class QuotesSpider(scrapy.Spider):
    name = "quotes"
    start_urls = [
        'http://quotes.toscrape.com/page/1/',
        'http://quotes.toscrape.com/page/2/',
    ]

    def parse(self, response):
        # parse 函數直接返回字典或者 Item 對象。
        for quote in response.css('div.quote'):
            yield {
                'text': quote.css('span.text::text').get(),
                'author': quote.css('small.author::text').get(),
                'tags': quote.css('div.tags a.tag::text').getall(),
            }

在爬取的命令上加上 -o 參數评腺,即可快速將結果保存到文件帘瞭,支持多種格式(csv,json歇僧,json lines图张,xml),也可方便地擴展自己的格式诈悍。

scrapy crawl quotes -o quotes.json

數據分頁了祸轮,還有下一頁怎么辦?拋出請求侥钳,讓 Scrapy 自己去處理适袜。



class QuotesSpider(scrapy.Spider):
    name = "quotes"
    start_urls = [
        'http://quotes.toscrape.com/page/1/',
    ]

    def parse(self, response):
        """
        parse 函數 yield 字典或者 Item 對象,則視為結果舷夺,
        yield 請求對象(follow 方法即是跟隨鏈接苦酱,快速生成對應的請求對象)即繼續(xù)爬取。
        """
        for quote in response.css('div.quote'):
            yield {
                'text': quote.css('span.text::text').get(),
                'author': quote.css('span small::text').get(),
                'tags': quote.css('div.tags a.tag::text').getall(),
            }

        next_page = response.css('li.next a').get()
        if next_page is not None:
            yield response.follow(next_page, callback=self.parse)

這就完了嗎给猾?當然不會疫萤,Scrapy 還提供了多種數據采集需要用到的功能。

  • 強大的擴展能力敢伸,快速編寫擴展和中間件扯饶。
  • 靈活的配置,并發(fā)控制,限速控制等尾序。
  • 自定義的爬取對象處理流水線钓丰。
  • 自定義的爬取對象存儲。
  • 自動統計數據每币。
  • 整合郵件携丁。
  • Telnet 控制臺等等。

這只是核心功能兰怠,還沒見到它的社區(qū)能力呢梦鉴!

  • Scrapyd:工程化部署爬蟲。
  • Scrapy-Splash:為 Scrapy 提供了 JS 渲染能力痕慢。
  • Scrapy Jsonrpc:Json RPC 服務控制爬蟲尚揣。
  • Gerapy:Web 爬蟲管理平臺涌矢。
  • ScrapyWeb:另一個 Web 爬蟲管理平臺掖举。
  • ScrapyKeeper:還是一個 Web 爬蟲管理平臺。
  • Portia:無需編碼的交互式爬蟲平臺娜庇。

這些就不再展開了塔次。

快速而又強大的數據采集利器,當屬 Scrapy名秀!

Pyspider

強大的瑞士軍刀 —— Pyspider。

Pyspider 可不得了匕得,它提供了一整套完整的數據采集解決方案,堪稱爬蟲界的“瑞士軍刀”汁掠。

  • 原生提供 Web 管理界面略吨,支持任務監(jiān)控翠忠、項目管理、結果查看等等乞榨。
  • 原生支持眾多的數據庫后端,如 MySQL吃既、MongoDB、SQLite鹦倚、Elasticsearch、Postgresql。
  • 原生支持多種消息隊列愤诱,如 RabbitMQ,Beanstalk淫半、Redis、Kombu科吭。
  • 支持任務優(yōu)先級、自動重試对人、定時任務谣殊、支持 JS 渲染等功能牺弄。
  • 分布式架構姻几。

爬蟲,就是這么簡單势告!

from pyspider.libs.base_handler import *

class Handler(BaseHandler):
    crawl_config = {
    }

    @every(minutes=24 * 60)
    def on_start(self):
        self.crawl('http://scrapy.org/', callback=self.index_page)

    @config(age=10 * 24 * 60 * 60)
    def index_page(self, response):
        for each in response.doc('a[href^="http"]').items():
            self.crawl(each.attr.href, callback=self.detail_page)

    def detail_page(self, response):
        return {
            "url": response.url,
            "title": response.doc('title').text(),
        }

啟動爬蟲框架蛇捌。

pyspider

然后,我們就可以通過 http://localhost:5000/ 進行爬蟲的管理和運行了咱台。

我們可以使用 css 選擇器快速提取網頁信息络拌。

    def index_page(self, response):
        for each in response.doc('a[href^="http"]').items():
            if re.match("http://www.imdb.com/title/tt\d+/$", each.attr.href):
                self.crawl(each.attr.href, callback=self.detail_page)
        self.crawl(response.doc('#right a').attr.href, callback=self.index_page)
        
    def detail_page(self, response):
        return {
            "url": response.url,
            "title": response.doc('.header > [itemprop="name"]').text(),
            "rating": response.doc('.star-box-giga-star').text(),
            "director": [x.text() for x in response.doc('[itemprop="director"] span').items()],
        }

啟用 PhantomJS 來渲染網頁上的 JS。

pyspider phantomjs

使用 fetch_type='js'回溺。

class Handler(BaseHandler):
    def on_start(self):
        self.crawl('http://www.twitch.tv/directory/game/Dota%202',
                   fetch_type='js', callback=self.index_page)

    def index_page(self, response):
        return {
            "url": response.url,
            "channels": [{
                "title": x('.title').text(),
                "viewers": x('.info').contents()[2],
                "name": x('.info a').text(),
            } for x in response.doc('.stream.item').items()]
        }

還能執(zhí)行一段 JS 代碼春贸,來獲取那些動態(tài)生成的網頁內容。

class Handler(BaseHandler):
    def on_start(self):
        self.crawl('http://www.pinterest.com/categories/popular/',
                   fetch_type='js', js_script="""
                   function() {
                       window.scrollTo(0,document.body.scrollHeight);
                   }
                   """, callback=self.index_page)

    def index_page(self, response):
        return {
            "url": response.url,
            "images": [{
                "title": x('.richPinGridTitle').text(),
                "img": x('.pinImg').attr('src'),
                "author": x('.creditName').text(),
            } for x in response.doc('.item').items() if x('.pinImg')]
        }

好了遗遵,接下來我知道萍恕,問題就是 PyspiderScrapy 選哪個?

簡單說下它們的對比瓮恭。

Scrapy 有更強大的擴展能力雄坪,社區(qū)更活躍,周邊更豐富屯蹦。而 Pyspider 本身功能更全维哈,但擴展能力較弱。許多 Scrapy 需要擴展實現的功能登澜,如 Web 界面阔挠、JS 渲染等,Pyspider 原生都提供了脑蠕。

Pyspider 的整套生態(tài)上手更容易购撼,實現更快速跪削。Scrapy 對復雜的場景有更多的選擇余地,更靈活迂求。

所以碾盐,諸位選哪款?

成年人需要做選擇嗎揩局?

后記

此上篇介紹了數據采集領域的三款神兵毫玖。

  • 樸實而又神奇的“接骨木劍” —— Requests
  • 快速而又強大的“百變神兵” —— Scrapy
  • 簡單而又全能的“瑞士軍刀” —— Pyspider

有此三款神兵在手,不信你不能馳騁“爬蟲”的江湖凌盯!

百媚生 Python《神兵譜》之數據分析-上篇付枫,如果覺得有用,請點贊關注收藏哦驰怎!

來自 知乎專欄阐滩。

參考

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市县忌,隨后出現的幾起案子掂榔,更是在濱河造成了極大的恐慌,老刑警劉巖芹枷,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡喧伞,警方通過查閱死者的電腦和手機潘鲫,發(fā)現死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進店門溉仑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人怨喘,你說我怎么就攤上這事振定。” “怎么了梳庆?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長驻售。 經常有香客問我芋浮,道長壳快,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任瘤旨,我火速辦了婚禮存哲,結果婚禮上七婴,老公的妹妹穿的比我還像新娘。我一直安慰自己修肠,他們只是感情好户盯,可當我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布莽鸭。 她就那樣靜靜地躺著,像睡著了一般足淆。 火紅的嫁衣襯著肌膚如雪礁阁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天裂逐,我揣著相機與錄音卜高,去河邊找鬼。 笑死掺涛,一個胖子當著我的面吹牛,可吹牛的內容都是我干的薪缆。 我是一名探鬼主播拣帽,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼减拭,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了拧粪?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤魄鸦,失蹤者是張志新(化名)和其女友劉穎拾因,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體盾致,經...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年穗酥,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片砾跃。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡抽高,死狀恐怖透绩,靈堂內的尸體忽然破棺而出壁熄,到底是詐尸還是另有隱情碳竟,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布昌执,位于F島的核電站懂拾,受9級特大地震影響铐达,放射性物質發(fā)生泄漏。R本人自食惡果不足惜娶桦,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一栗涂、第九天 我趴在偏房一處隱蔽的房頂上張望祈争。 院中可真熱鬧,春花似錦菩混、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽疟游。三九已至,卻和暖如春颁虐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背儒陨。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留察绷,地道東北人津辩。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像闸度,于是被迫代替她去往敵國和親蚜印。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,864評論 2 354