python网络爬虫——数据采集

前言:

之所以叫网络爬虫(Web crawler)是因为它们可以沿着网络爬行。它们的本质就是一种递归方式。为了找到 URL 链接,它们必须首先获取网页内容,检查这个页面的内容,再寻找另一个 URL,然后获取 URL 对应的网页内容,不断循环这一过程。

1 遍历单个域名

示例:写一段获取维基百科网站的任何页面并提取页面链接的 Python 代码

from urllib.request import urlopen
from bs4 import BeautifulSoup

html = urlopen("http://en.wikipedia.org/wiki/Kevin_Bacon")
bsObj = BeautifulSoup(html)
for link in bsObj.findAll("a"):
    if 'href' in link.attrs:
    print(link.attrs['href'])

观察生成的一列链接,会发现“词条链接”和“其他链接”的差异,会发现“词条链接”都有三个共同点:

  • 它们都在 id 是 bodyContent 的 div 标签里
  • URL 链接不包含分号
  • URL 链接都以 /wiki/ 开头

我们可以利用这些规则稍微调整一下代码来获取词条链接:

for link in bsObj.find("div", {"id":"bodyContent"}).findAll("a",href=re.compile("^(/wiki/)((?!:).)*$")):
    if 'href' in link.attrs:
    print(link.attrs['href'])

完善一下,实现实现了在一个网站上随机地从一个链接跳到另一个链接:

from urllib.request import urlopen
from bs4 import BeautifulSoup
import datetime
import random
import re

random.seed(datetime.datetime.now())
def getLinks(articleUrl):
    html = urlopen("http://en.wikipedia.org"+articleUrl)
    bsObj = BeautifulSoup(html)
    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)

2 采集整个网站

[$]补充:网站中有深网、暗网、浅网。深网是网络的一部分,与浅网(surface Web)对立。浅网是互联网上搜索引擎可以抓
到的那部分网络。据不完全统计,互联网中其实约 90% 的网络都是深网。暗网,也被称为 Darknet 或 dark Internet,完全是另一种“怪兽”。它们也建立在已有的网络基础上,但是使用 Tor 客户端,带有运行在 HTTP 之上的新协议,提供了一个信息交换的安全隧道。

一个常用的费时的网站采集方法就是从顶级页面开始(比如主页),然后搜索页面上的所有链接,形成列表。再去采集这些链接的每一个页面,然后把在每个页面上找到的链接形成新的列表,重复执行下一轮采集。

[*]注意:为了避免一个页面被采集两次,链接去重是非常重要的。

from urllib.request import urlopen
from bs4 import BeautifulSoup
import re

pages = set()
def getLinks(pageUrl):
    global pages
    html = urlopen("http://en.wikipedia.org"+pageUrl)
    bsObj = BeautifulSoup(html)
    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(newPage)
                pages.add(newPage)
                getLinks(newPage)
getLinks("")

[*] 注意:如果递归运行的次数非常多,前面的递归程序就很可能崩溃。
Python 默认的递归限制(程序递归地自我调用次数)是 1000 次。 

为了有效地使用它们,在用爬虫的时候我们需要在页面上做些事情:

from urllib.request import urlopen
from bs4 import BeautifulSoup
import re

pages = set()
def getLinks(pageUrl):
    global pages
    html = urlopen("http://en.wikipedia.org"+pageUrl)
    bsObj = BeautifulSoup(html)
    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/)")):
        ...#与上面一样

3 通过互联网采集

几个灵活的 Python 函数组合起来就可以实现不同类型的网络爬虫,用不超过 50 行代码就可轻松地写出来:

from urllib.request import urlopen
from bs4 import BeautifulSoup
import re
import datetime
import random

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)
    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")

网站首页上并不能保证一直能发现外链。这时为了能够发现外链,就需要用一种类似前面案例中使用的采集方法,即递归地深入一个网站直到找到一个外链才停止。 

如果我们的目标是采集一个网站所有的外链,并且记录每一个外链,我们可以增加下面的函数:

# 收集网站上发现的所有外链列表
allExtLinks = set()
allIntLinks = set()
def getAllExternalLinks(siteUrl):
    html = urlopen(siteUrl)
    bsObj = BeautifulSoup(html)
    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")

[*]注意:服务器端重定向,你通常不用担心。如果你在用 Python 3.x 版本的 urllib 库,它会自动处理重定向。 

4 用Scrapy采集

虽然写 Scrapy 爬虫很简单,但完成一个爬虫还是需要一些设置。如果在当前目录下创建新的 Scrapy 项目,就执行下面的代码:
$scrapy startproject wikiSpider(wikiSpider 是新项目的名称。)

文件夹的目录结构如下所示:
• scrapy.cfg
    — wikiSpider
        — __init.py__
        — items.py
        — pipelines.py
        — settings.py
        — spiders
        — __init.py__

为 了 创 建 一 个 爬 虫, 我 们 需 要 在 wikiSpider/wikiSpider/spiders/ 文 件 夹 里 增 加 一 个articleSpider.py 文件。另外,在 items.py 文件中,我们需要定义一个 Article 类。 items.py 文件应该像下面这样:

# -*- coding: utf-8 -*-
# Define here the models for your scraped items
#
# See documentation in:
# http://doc.scrapy.org/en/latest/topics/items.html
from scrapy import Item, Field
class Article(Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    title = Field()

Scrapy 的每个 Item (条目)对象表示网站上的一个页面。

在新建的 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 crawl article

这行命令会用条目名称 article 来调用爬虫。

[$]补充:

可以在 Scrapy 项目中的 setting.py 文件中设置日志显示层级:
LOG_LEVEL = 'ERROR'
Scrapy 日志有五种层级,按照范围递增顺序排列如下:
•  CRITICAL
•  ERROR
•  WARNING
•  DEBUG
•  INFO
如果日志层级设置为 ERROR ,那么只有 CRITICAL 和 ERROR 日志会显示出来。
如果日志层级设置为 INFO ,那么所有信息都会显示出来,其他同理。


日志不仅可以显示在终端,也可以通过下面命令输出到一个独立的文件中:
$ scrapy crawl article -s LOG_FILE=wiki.log

Scrapy 支持用不同的输出格式来保存这些信息,比如 CSV、JSON 或 XML 文件格式,对应命令如下所示:
$ scrapy crawl article -o articles.csv -t csv
$ scrapy crawl article -o articles.json -t json
$ scrapy crawl article -o articles.xml -t xml

Scrapy 是处理网络数据采集相关问题的利器。它可以自动收集所有 URL,然后和指定的规则进行比较;确保所有的 URL 是唯一的;根据需求对相关的 URL 进行标准化;以及到更深层的页面中递归查找。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值