如何用Python和Scrapy从网站抓取数据?

290 阅读9分钟

如何用Python和Scrapy将网络变成数据

自成立以来,网站就被用来分享信息。无论是维基百科的文章、YouTube频道、Instagram账户,还是Twitter手柄。它们都充满了有趣的数据,每个人只要能上网和使用网络浏览器,就可以获得。

但是,如果我们想以编程方式获得任何特定的数据呢?

有两种方法可以做到这一点:

  1. 使用官方API
  2. 网络刮削

API(应用编程接口)的概念是为了在不同的系统之间以标准的方式交换数据。但是,大多数时候,网站所有者并不提供任何API。在这种情况下,我们只剩下使用网络刮削来提取数据的可能性。

基本上,每个网页都是以HTML格式从服务器返回的。这意味着,我们的实际数据被很好地包装在HTML元素中。这使得检索特定数据的整个过程变得非常简单和直接。

本教程将是你学习使用Python编程语言进行网络刮削的终极指南。首先,我将通过一些基本的例子让你熟悉网络抓取。稍后,我们将利用这些知识从Livescore.cz提取足球比赛的数据。

不多说了,让我们跟着我一起去吧。


开始吧

为了让我们开始,你需要启动一个新的Python3项目,并安装 Scrapy (一个Python的网络刮擦和网络爬行库)。我在本教程中使用pipenv,但你可以使用 pip 和 venv,或者 conda。

pipenv install scrapy

在这一点上,你有了Scrapy,但你仍然需要创建一个新的网络抓取项目,为此,Scrapy为我们提供了一个命令行,为我们做这些工作。

现在让我们通过使用scrapy cli创建一个名为web_scraper 的新项目。

如果你像我一样使用pipenv ,请使用。

pipenv run scrapy startproject web_scraper .

否则,从你的虚拟环境中使用

scrapy startproject web_scraper .

这将在当前目录下创建一个基本项目,结构如下。

scrapy.cfg
web_scraper/
    __init__.py
    items.py
    middlewares.py
    pipelines.py
    settings.py
    spiders/
        __init__.py

用XPath查询建立我们的第一个蜘蛛

我们将以一个非常简单的例子开始我们的网络刮擦教程。首先,我们将在HTML中找到Live Code Stream网站的标志。我们知道它只是一个文本,而不是一个图像,所以我们将简单地提取这个文本。

代码

为了开始工作,我们需要为这个项目创建一个新的spider。我们可以通过创建一个新的文件或使用CLI来实现。

由于我们已经知道我们需要的代码,我们将在这个路径上创建一个新的Python文件 /web_scraper/spiders/live_code_stream.py

下面是这个文件的内容。

import scrapy

class LiveCodeStreamSpider(scrapy.Spider):
    name = "lcs"

    start_urls = [
        "https://livecodestream.dev/"
    ]

    def parse(self, response):
        yield {
            'logo': response.xpath("/html/body/header/nav/a[1]/text()").get()
        }

代码解释

  1. 首先,我们导入了Scrapy库。这是因为我们需要它的功能来创建一个Python网络蜘蛛。然后这个蜘蛛将被用来抓取指定的网站并从中提取有用的信息。

  2. 我们创建了一个类,并将其命名为LiveCodeStreamSpider 。基本上,它继承了scrapy.Spider ,这就是为什么我们把它作为一个参数传递。

  3. 现在,一个重要的步骤是用一个叫做name 的变量为你的蜘蛛定义一个独特的名字。请记住,你不允许使用现有蜘蛛的名字。同样地,你也不能用这个名字来创建新的蜘蛛。它在整个项目中必须是唯一的。

  4. 之后,我们使用start_urls 列表传递网站的URL。

  5. 最后,创建一个名为parse() 的方法,该方法将在HTML代码内定位标志并提取其文本。在Scrapy中,有两种方法可以在源代码里面找到HTML元素。下面提到了这些方法。

    • CSS
    • XPath

    你甚至可以使用一些外部库,如BeautifulSouplxml。但是,在这个例子中,我们使用了XPath。

    确定任何HTML元素的XPath的一个快速方法是在Chrome DevTools中打开它。现在,只需在该元素的HTML代码上点击右键,将鼠标指针悬停在刚刚出现的弹出菜单内的 "复制 "上。最后,点击 "复制XPath "菜单项。

    顺便说一下,我在元素的实际XPath后面使用了/text() ,只检索该元素的文本,而不是完整的元素代码。

注意: 你不允许为上面提到的变量、列表或函数使用任何其他名称。这些名字是在Scrapy库中预先定义的。所以,你必须按原样使用它们。否则,该程序将不能按预期工作。

运行 "蜘蛛"

由于我们已经在命令提示符中的web_scraper文件夹内。让我们执行我们的蜘蛛,并使用下面的代码将结果填入一个新文件lcs.json中。是的,我们得到的结果将是使用JSON格式的良好结构。

pipenv run scrapy crawl lcs -o lcs.json
scrapy crawl lcs -o lcs.json

结果

当上述代码执行后,我们会在项目文件夹中看到一个新文件lcs.json

下面是这个文件的内容。

[
{"logo": "Live Code Stream"}
]

另一个使用CSS查询选择器的蜘蛛

我们大多数人都喜欢运动,而说到足球,它是我个人的最爱。

世界各地经常举办足球比赛。有几个网站在比赛进行时提供比赛结果的直播。但是,这些网站大多不提供任何官方API。

反过来,这也为我们创造了一个机会,可以利用我们的网络刮擦技能,通过直接刮擦他们的网站来提取有意义的信息。

例如,让我们看一下Livescore.cz网站。

在他们的主页上,他们很好地展示了今天(你访问该网站的日期)将进行的锦标赛和比赛。

我们可以检索到这样的信息

  • 锦标赛名称
  • 比赛时间
  • 球队1名称(如国家、足球俱乐部等)。
  • 球队1的目标
  • 球队2名称(如国家、足球俱乐部等)。
  • 2队进球
  • 等等。

在我们的代码示例中,我们将提取今天有比赛的锦标赛名称。

代码

让我们在我们的项目中创建一个新的spider来检索锦标赛的名称。我将这个文件命名为livescore_t.py

以下是你需要在 /web_scraper/web_scraper/spiders/livescore_t.py中输入的代码

import scrapy

class LiveScoreT(scrapy.Spider):
    name = "LiveScoreT"

    start_urls = [
        "https://livescore.cz/"
    ]

    def parse(self, response):
        for ls in response.css('#soccer_livescore .tournament'):
            yield {
                'tournament': ls.css('.nation a::text').get()
            }

代码解释

  1. 像往常一样,导入Scrapy。

  2. 创建一个继承了scrapy.Spider属性和功能的类。

  3. 给我们的蜘蛛起一个独特的名字。在这里,我使用了LiveScoreT ,因为我们将只提取锦标赛的名称。

  4. 下一步是提供Livescore.cz的URL。

  5. 最后,parse() 函数循环处理所有包含赛事名称的匹配元素,并使用yield 将其连接起来。最后,我们收到所有今天有匹配的锦标赛名称。

    需要注意的是,这次我使用了CSS选择器而不是XPath

运行新创建的spider

现在是时候看看我们的蜘蛛的行动了。运行下面的命令,让蜘蛛抓取Livescore.cz网站的主页。网络抓取的结果将被添加到一个JSON格式的名为ls_t.json的新文件中。

pipenv run scrapy crawl LiveScoreT -o ls_t.json

现在你已经知道该怎么做了。

结果

这是我们的网络蜘蛛在2020年11月18日从Livescore.cz提取的内容。记住,输出结果可能每天都在变化。

[
{"tournament": "International - World Cup Qualification CONMEBOL"},
{"tournament": "Brazil - Serie A"},
{"tournament": "International - UEFA Nations League A Grp. 3"},
{"tournament": "International - UEFA Nations League A Grp. 4"},
{"tournament": "International - UEFA Nations League C Grp. 1"},
{"tournament": "International - UEFA Nations League D Grp. 1"},
{"tournament": "International - UEFA Nations League D Grp. 2"},
{"tournament": "..."}
]

一个更高级的用例

在这一节中,我们将不再仅仅检索比赛的名称。我们将更进一步,获得完整的锦标赛和他们的比赛的细节。

在**/web_scraper/web_scraper/spiders/中创建一个新文件,并将其命名为livescore.py**。现在,在其中输入以下代码。

import scrapy

class LiveScore(scrapy.Spider):
    name = "LiveScore"

    start_urls = [
        "https://www.livescore.cz/yesterday.php"
    ]

    def parse(self, response):
        table_tr = response.css('tr')
        
        tournaments = []

        for tr in table_tr:
          if tr.css('.tournament'):
            tournaments.append({
                    'name': tr.css('.nation a::text').get(),
                    'matches': []
                })
          elif tr.css('.match'):
            team_score = tr.css('.col-score strong::text').get()

            if team_score is not None:
                team_1_score = team_score.split(':')[0]
                team_2_score = team_score.split(':')[1]
            else:
                team_1_score = None
                team_2_score = None

            tournaments[-1]['matches'].append({
                'time': tr.css('.match .col-time time::attr(datetime)').get(),
                'state': tr.css('.match .col-state span::text').get(),
                'team_1_name': tr.css('.col-home a::text').get(),
                'team_1_score': team_1_score,
                'team_2_name': tr.css('.col-guest a::text').get(),
                'team_2_score': team_2_score
            })
        
        for t in tournaments:
            yield {
                'tournament': t
            }

代码解释

这个文件的代码结构与我们之前的例子相同。在这里,我们只是用新的功能更新了parse() 方法。

基本上,我们从页面中提取了所有的HTML<tr></tr> 元素。然后,我们对它们进行循环,找出它是一个锦标赛还是一场比赛。如果它是一个锦标赛,我们提取它的名字。如果是比赛,我们提取了它的 "时间"、"状态 "和 "两队的名称和得分"。

运行这个例子

在控制台中输入以下命令并执行。

pipenv run scrapy crawl LiveScore -o ls.json

结果

下面是它检索到的一个样本。

[{
    "tournament": {
        "name": "International - World Cup Qualification CONMEBOL",
        "matches": [{
            "time": "2020-11-18T00:00:00+01:00",
            "state": null,
            "team_1_name": "Uruguay",
            "team_1_score": "0",
            "team_2_name": "Brazil",
            "team_2_score": "2"
        }, {
            "time": "2020-11-18T00:00:00+01:00",
            "state": null,
            "team_1_name": "Paraguay",
            "team_1_score": "2",
            "team_2_name": "Bolivia",
            "team_2_score": "2"
        }, {
            "time": "2020-11-18T01:30:00+01:00",
            "state": null,
            "team_1_name": "Peru",
            "team_1_score": "0",
            "team_2_name": "Argentina",
            "team_2_score": "2"
        }]
    }
}]

现在有了这些数据,我们可以做任何我们想做的事情,比如用它来训练我们自己的神经网络来预测未来的比赛:p。


结论

数据分析师经常使用网络刮削,因为它有助于他们收集数据以预测未来。同样,企业也使用它从网页中提取电子邮件,因为它是一种有效的线索生成方式。我们甚至可以用它来监测产品的价格。

换句话说,网络刮削有很多用例,而Python完全有能力做到这一点。

那么,你还在等什么呢?现在就试试搜刮你喜欢的网站吧。

谢谢你的阅读!