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部分增加相应的代码即可。