1. 程式人生 > >《Python網絡數據采集》讀書筆記(四)

《Python網絡數據采集》讀書筆記(四)

wiki 維基 scrapy

1、遍歷單個域名

維基百科那些指向詞條頁面(不是指向其他內容頁面)的鏈接有三個共同點:

? 它們都在id是bodyContent的div標簽裏

? URL鏈接不包含分號

? URL鏈接都以/wiki/開頭

# -*- coding: utf-8 -*-
import re
from urllib.request import urlopen
from bs4 import BeautifulSoup


html = urlopen("http://en.wikipedia.org/wiki/Kevin_Bacon")
bsObj = BeautifulSoup(html, "lxml")
for link in bsObj.find("div", {"id":"bodyContent"}).findAll("a", href=re.compile("^(/wiki/)((?!:).)*$")):
    if 'href' in link.attrs:
        print(link.attrs['href'])

運行以上代碼,就會看到維基百科上凱文·貝肯詞條裏所有指向其他詞條的鏈接。


簡單地構建一個從一個頁面到另一個頁面的爬蟲:

# -*- coding: utf-8 -*-
import re
import datetime
import random
from urllib.request import urlopen
from bs4 import BeautifulSoup

# 用系統當前時間生成一個隨機數生成器
random.seed(datetime.datetime.now())
def getLinks(articleUrl):
    html = urlopen("http://en.wikipedia.org"+articleUrl)
    bsObj = BeautifulSoup(html ,"lxml")
    return bsObj.find("div", {"id":"bodyContent"}).findAll("a", href=re.compile("^(/wiki/)((?!:).)*$"))
links = getLinks("/wiki/Kevin_Bacon")
while len(links) > 0:
    newArticle = links[random.randint(0, len(links)-1)].attrs["href"]
    print(newArticle)
    links = getLinks(newArticle)

程序首先把起始頁面裏的詞條鏈接列表設置成鏈接列表。然後用一個循環,從頁面中隨機找一個詞條鏈接標簽並抽取href屬性,打印這個頁面鏈接,再把這個鏈接傳入getLinks函數重新獲取新的鏈接列表。


2、采集整個網站

首先要做的就是對鏈接去重,以避免一個頁面被重復采集。

接著我們可以打印出“頁面標題、正文的第一個段落,以及編輯頁面的鏈接(如果有的話)”。

# -*- coding: utf-8 -*-
import re
from urllib.request import urlopen
from bs4 import BeautifulSoup


pages = set()
def getLinks(pageUrl):
    global pages
    html = urlopen("http://en.wikipedia.org"+pageUrl)
    bsObj = BeautifulSoup(html, "lxml")
    try:
        print(bsObj.h1.get_text())
        print(bsObj.find(id="mw-content-text").findAll("p")[0])
        print(bsObj.find(id="ca-edit").find("span").find("a").attrs['href'])
    except AttributeError:
        print("頁面缺少一些屬性!不過不用擔心!")
        
    for link in bsObj.findAll("a", href=re.compile("^(/wiki/)")):
        if 'href' in link.attrs:
            if link.attrs['href'] not in pages:
            # 我們遇到了新頁面
                newPage = link.attrs['href']
                print("----------------\n"+newPage)
                pages.add(newPage)
                getLinks(newPage)
getLinks("")

以上程序一開始用getLinks處理一個空URL(其實是維基百科的主頁)。接著打印出需要輸出的信息,然後遍歷頁面上的每個鏈接,並檢查是否已經在全局變量集合pages裏面了(已經采集的頁面集合)。如果不在,就打印到屏幕上,並把鏈接加入pages 集合,再用getLinks遞歸地處理這個鏈接。


3、通過互聯網采集

# -*- coding: utf-8 -*-
import re
import datetime
import random
from urllib.request import urlopen
from bs4 import BeautifulSoup


pages = set()
random.seed(datetime.datetime.now())

# 獲取頁面所有內鏈的列表
def getInternalLinks(bsObj, includeUrl):
    internalLinks = []
    # 找出所有以"/"開頭的鏈接
    for link in bsObj.findAll("a", href=re.compile("^(/|.*"+includeUrl+")")):
        if link.attrs['href'] is not None:
            if link.attrs['href'] not in internalLinks:
                internalLinks.append(link.attrs['href'])
    return internalLinks
    
# 獲取頁面所有外鏈的列表
def getExternalLinks(bsObj, excludeUrl):
    externalLinks = []
    # 找出所有以"http"或"www"開頭且不包含當前URL的鏈接
    for link in bsObj.findAll("a", href=re.compile("^(http|www)((?!"+excludeUrl+").)*$")):
        if link.attrs['href'] is not None:
            if link.attrs['href'] not in externalLinks:
                externalLinks.append(link.attrs['href'])
    return externalLinks
    
def splitAddress(address):
    addressParts = address.replace("http://", "").split("/")
    return addressParts
    
def getRandomExternalLink(startingPage):
    html = urlopen(startingPage)
    bsObj = BeautifulSoup(html, "lxml")
    externalLinks = getExternalLinks(bsObj, splitAddress(startingPage)[0])
    if len(externalLinks) == 0:
        internalLinks = getInternalLinks(startingPage)
        return getNextExternalLink(internalLinks[random.randint(0, len(internalLinks)-1)])
    else:
        return externalLinks[random.randint(0, len(externalLinks)-1)]
        
def followExternalOnly(startingSite):
    externalLink = getRandomExternalLink("http://oreilly.com")
    print("隨機外鏈是:"+externalLink)
    followExternalOnly(externalLink)
    
followExternalOnly("http://oreilly.com")

上面這個程序從http://oreilly.com開始,然後隨機地從一個外鏈跳到另一個外鏈。

網站首頁上並不能保證一直能發現外鏈。這時為了能夠發現外鏈,就需要遞歸地深入一個網站直到找到一個外鏈才停止。如果爬蟲遇到一個網站裏面一個外鏈都沒有,這時程序就會一直在這個網站運行跳不出去,直到遞歸到達Python的限制為止。


如果我們的目標是采集一個網站所有的外鏈,並且記錄每一個外鏈,可以增加下面的函數:

allExtLinks = set()
allIntLinks = set()
def getAllExternalLinks(siteUrl):
    html = urlopen(siteUrl)
    bsObj = BeautifulSoup(html, 'lxml')
    internalLinks = getInternalLinks(bsObj,splitAddress(siteUrl)[0])
    externalLinks = getExternalLinks(bsObj,splitAddress(siteUrl)[0])
    for link in externalLinks:
        if link not in allExtLinks:
            allExtLinks.add(link)
            print(link)
    for link in internalLinks:
        if link not in allIntLinks:
            print("即將獲取鏈接的URL是:"+link)
            allIntLinks.add(link)
            getAllExternalLinks(link)
getAllExternalLinks("http://oreilly.com")


4、用Scrapy采集

創建Scrapy項目:在當前目錄中會新建一個名稱也是wikiSpider的項目文件夾。

scrapy startproject wikiSpider


在items.py文件中,定義一個Article類。

# -*- coding: utf-8 -*-
# Define here the models for your scraped items
#
# See documentation in:
#  

from scrapy import Item, Field


class Article(Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    title = Field()


在wikiSpider/wikiSpider/spiders/文件夾裏增加一個articleSpider.py文件。

from scrapy.selector import Selector
from scrapy import Spider
from wikiSpider.items import Article


class ArticleSpider(Spider):
    
    name = "article"
    allowed_domains = ["en.wikipedia.org"]
    start_urls = ["http://en.wikipedia.org/wiki/Main_Page", "http://en.wikipedia.org/wiki/Python_%28programming_language%29"]
    
    def parse(self, response):
        item = Article()
        title = response.xpath('//h1/text()')[0].extract()
        print("Title is: " + title)
        item['title'] = title
        return item


在wikiSpider主目錄中用如下命令運行ArticleSpider:

scrapy startproject wikiSpider

陸續出現的調試信息中應該會這兩行結果:

Title is: Main Page
Title is: Python (programming language)


*可以在Scrapy項目中的setting.py文件中設置日誌顯示層級:

LOG_LEVEL = 'ERROR'

Scrapy日誌有五種層級,按照範圍遞增順序排列如下:CRITICAL,ERROR,WARNING,DEBUG,INFO

也可以輸出(追加)到一個獨立的文件中:

scrapy crawl article -s LOG_FILE=wiki.log
Title is: Main Page
Title is: Python (programming language)


Scrapy支持用不同的輸出格式來保存這些信息,對應命令如下所示:

scrapy crawl article -o articles.csv -t csv
scrapy crawl article -o articles.json -t json
scrapy crawl article -o articles.xml -t xml

也可以自定義Item對象,把結果寫入你需要的一個文件或數據庫中,只要在爬蟲的parse部分增加相應的代碼即可。


《Python網絡數據采集》讀書筆記(四)