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

前言

刀槍劍戟,斧鉞鉤叉,钂鐮槊棒,鞭鐗錘抓。css

神兵在手,妖魔不怕,劈荊斬棘,濺血生花。html

行走江湖,誰沒有件趁手的兵器。python

可是,兵器有帶楞的,有帶刃兒的,有帶戎繩的,有帶鎖鏈兒的,五花八門,對於新手來講,真的是「亂花漸欲迷人眼」。git

不過,古有江湖百曉生,今有 Python 百媚生。百曉生所著的《兵器譜》讓江湖血雨腥風,這百媚生也編纂了一部 Python 《神兵譜》,不知可否讓 Python 江湖掀起什麼暴雨狂風?github

咱們今天就來說講這《神兵譜》的「數據分析」篇。這「數據分析」篇又分爲上、中、下三篇,分別針對數據分析的數據採集、數據處理及數據可視化三個方面。web

本文不光是神兵的展現,更要教會你們簡單的使用,可以幫助你們挑選合適趁手的兵器,才能在刀光劍影的江湖,立於不敗之地。sql

話很少說,直入主題。數據庫

上篇:數據採集

說到數據採集,那最大名鼎鼎的方式就是「爬蟲」啦,讓咱們來看看百媚生帶給咱們的「爬蟲」利器吧,是否是真如傳言的「見血封喉」呢?json

Requests

啥?爲何 requests 是「爬蟲」?後端

可不要小瞧了它!雖然說 requests 是網絡請求庫,但它卻如高手手中的「木劍」通常,用好了,同樣招招致命。

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

>>> 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()
# 遍歷節點
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 語法快速定位節點,提取數據
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,分分鐘讓咱們全副武裝。

# 建立一個項目
scrapy startproject tutorial
cd tutorial
# 建立一個爬蟲
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
複製代碼

這尚未發揮 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 方法便是跟隨連接,快速生成對應的請求對象)即繼續爬取。 """
        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 還提供了多種數據採集須要用到的功能。

  • 強大的擴展能力,快速編寫擴展和中間件。
  • 靈活的配置,併發控制,限速控制等。
  • 自定義的爬取對象處理流水線。
  • 自定義的爬取對象存儲。
  • 自動統計數據。
  • 整合郵件。
  • Telnet 控制檯等等。

這只是核心功能,還沒見到它的社區能力呢!

這些就再也不展開了。

快速而又強大的數據採集利器,當屬 Scrapy

Pyspider

強大的瑞士軍刀 —— Pyspider。

Pyspider 可不得了,它提供了一整套完整的數據採集解決方案,堪稱爬蟲界的「瑞士軍刀」。

  • 原生提供 Web 管理界面,支持任務監控、項目管理、結果查看等等。
  • 原生支持衆多的數據庫後端,如 MySQL、MongoDB、SQLite、Elasticsearch、Postgresql。
  • 原生支持多種消息隊列,如 RabbitMQ,Beanstalk、Redis、Kombu。
  • 支持任務優先級、自動重試、定時任務、支持 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()]
        }
複製代碼

還能執行一段 JS 代碼,來獲取那些動態生成的網頁內容。

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 有更強大的擴展能力,社區更活躍,周邊更豐富。而 Pyspider 自己功能更全,但擴展能力較弱。許多 Scrapy 須要擴展實現的功能,如 Web 界面、JS 渲染等,Pyspider 原生都提供了。

Pyspider 的整套生態上手更容易,實現更快速。Scrapy 對複雜的場景有更多的選擇餘地,更靈活。

因此,諸位選哪款?

成年人須要作選擇嗎?

後記

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

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

有此三款神兵在手,不信你不能馳騁「爬蟲」的江湖!

百媚生 Python《神兵譜》之數據分析-上篇,若是以爲有用,請點贊關注收藏哦!

參考

相關文章
相關標籤/搜索