《Python網絡數據采集》讀書筆記(四)
維基百科那些指向詞條頁面(不是指向其他內容頁面)的鏈接有三個共同點:
? 它們都在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網絡數據采集》讀書筆記(四)