欢迎大家,希望你有一个美好的一天!今天我们将构建一个简单的 python 网页抓取脚本,根据您想要的任何日期、月份或年份来抓取您最喜欢的medium.com出版物的热门文章!如果你坚持到最后,我将向你展示如何使用 pythonsThreading模块,这会导致极快的网络抓取。

以下是我们今天要使用的工具:

  • Python 编程语言

  • 美汤套餐

  • 请求

  • 线程模块

  • 文件系统集成

目录:
  • 什么是网页抓取?

  • 编写脚本

*你需要什么模块?

*如何从 Medium 上的出版物中获取文章?

*如何进行网络请求?

*如何用 Beautiful Soup 解析 HTML?

*如何将数据写入文件?

*BONUS — 如何使用线程提高性能

  • 源代码

什么是网页抓取?

在深入研究代码之前,我可能应该解释一下网络抓取到底是什么。网络抓取是从互联网上提取信息,很简单吧?好的,所以我们知道什么是网络抓取,但我们为什么需要它,它有什么用处?

网络抓取是如此广泛,以至于我们无法将其缩小到单一用途,但网络抓取的一些常见需求包括数据收集和自动化重复任务(嗯,这听起来非常有用)。当然,您几乎可以手动执行任何可以进行网络抓取的操作,但如果您像我一样,您更愿意避免无聊、重复的任务。加上编写一些代码来为你工作,听起来就像是在推进你的生活。

你需要什么模块?

在开始编码之前,我们必须确保安装了所有正确的包。对于这个脚本,我们需要使用 pip 安装requestsbeautifulsoup4模块。

pip install requests
pip install beautifulsoup4

requests模块是我们实际向Medium.com发出网络请求并获取其数据的方式,而beautifulsoup4模块将为我们解析这些数据并返回我们真正关心的数据。 (哦,从现在开始我将把 BeautifulSoup 模块称为 bs4)

如果你想深入了解这些模块,这里有两篇来自_Real Python_的精彩文章,requests,bs4

现在我们已经安装了模块,我们将创建一个get_medium_articles.py脚本并导入所有内容。

Imports.png

如何从 Medium 上的出版物中获取文章?

在开始获取数据之前,我们需要了解的是真正了解我们需要到达的 URL。在 Medium 中,每个出版物都有自己唯一的 URL,这很好,但 Medium 对我们来说更进一步,因为我们希望根据发布日期找到不同的文章。

我们如何实现这一目标?好吧,我们所要做的就是添加

/archive/YEAR-MONTH-DAY

到 URL 的末尾和指定日期的热门文章。这让我们的生活更轻松,因为我们所要做的就是生成我们想要的日期(这是datetime模块的来源),将其添加到发布 URL,然后我们就可以开始获取数据了。

因此,让我们首先查找所有您喜欢的出版物并创建一个字典,该字典将出版物名称存储为键,并将其 URL 作为值,并在末尾附加 /archive/

您可能会注意到我们没有在此处添加任何日期,那是因为我们将通过程序动态生成它们(我将坚持使用当前日期,但您可以完全控制它)。一个提示是,您可以排除日期中的日期或月份,以获取某个月份或年份的热门文章。

URLS.png

创建 URL 字典后,我们将创建一个名为fetch_articles的函数,它将接收出版物和 URL 并返回我们想要的数据!

让我们继续初始化一个名为data的空字典,然后让我们继续获取当前日期,我们将需要datetime.now()函数返回当前日期和时间,然后在日期上使用strftime方法以某种方式正确格式化它会明白。

只是一个快速提示,如果您在一大早这样做,那么很有可能还没有任何文章,所以从前一天获取可能是个好主意,如果是这种情况,您可以将get_articles函数中的日期更改为

yesterday = datetime.datetime.now() - datetime.timedelta(days=1)
date = str(yesterday.strftime("%Y/%m/%d"))

接下来,我们需要将日期附加到 URL 的末尾并打印出出版物给我们一些反馈,或者如果您愿意,您可以在此处打印出 URL。 (如果您好奇,可以继续查看 URL 并更改日期,看看会发生什么!)

如何进行网络请求?

对于这一步,我们需要做的就是执行一个 GET 请求。什么是 GET 请求?这里我们需要知道的是,它是否会转到一个 URL 并从该 URL 获取所有数据,这可以是 JSON、HTML 等。在这种情况下,我们将得到的是我们将输入的 HTML bs4 来获取我们的数据。所以事不宜迟,让我们开始构建我们的函数😄。

make_request.png

我们通过调用requests.get()并将 URL 传递给它来发出 GET 请求。这将返回一个响应对象,我们要做的第一件事是调用raise_for_status(),因为我们希望在编码时尽可能早地崩溃,因为它总是会在更接近问题的地方崩溃。

在这种情况下,如果您提供了无效的 URL,或者存在连接问题,程序将会崩溃。

我决定捕捉这个异常并打印出无效的 URL。只要请求有效,我们就可以通过在响应对象上调用.content来访问请求中的 HTML,这就是我们接下来要提供 bs4 的内容!

好的,你们中的一些人一定会问,“allow_redirects=False是做什么的?”。如果你发现了,那就太好了,但让我解释一下。

在 Medium 上,如果您在没有文章发表的那一天通过,那么它将重定向到文章发表的上个月,并且因为我只关心从今天开始查找所有新的相关文章,所以我选择禁止重定向。如果这是您想要的功能,那么只需设置allow_redirects=True

如何用 Beautiful Soup 解析 HTML?

在我们实际对代码进行任何操作之前,让我们访问您选择的任何出版物并输入您要查看文章的日期,我将转到betterprogramming.pub/archive/2021/08/18,然后打开起来我的检查员。

Inspector.png

如果您使用选择器工具(command/control+shift+c)并将鼠标悬停在一篇文章上,您将看到一个类名为class="postArticle postArticle--short js-postArticle js-trackPostPresentation js-trackPostScrolls"的 div,这就是 bs4 将用来查找页面上每篇文章的内容。

div.png

现在我们知道要查找什么,让我们创建一个BeautifulSoup对象并传入页面的内容,对于第二个参数,我们将传入html.parser,它告诉 bs4 解析 HTML。回过头来看,我们可以看到这些类都在一个 div 中,因此我们需要告诉我们的新 bs4 对象来查找页面上具有这些特定类的所有 div。

fetch_articles.png

得到所有文章后,如果我们发现任何文章,我们会给自己一些终端反馈。接下来,我们将确定我们想要多少篇文章,我选择每个出版物 3 篇(或更少),但您可以拥有任意多的文章。

save_articles.png

哇,代码太多了,下一部分可以更进一步,但我选择只从文章中检索标题和 URL(我挑战你也得到作者!)。让我们分解它并将 HTML 转换为我们可以使用的东西!

目前,我们有一个文章列表,所以让我们一个一个循环并提取数据。首先,我们将使用find()方法提取标题,对其进行验证,然后检索文本字符串。

下一行看起来很混乱,但让我们看看,发现我们正在找到所有a标签(链接),找到合适的文本,得到它是href,从 URL 中删除参数然后返回它。

我计划存储数据的方式是有一个以出版物为键的字典,其值为文章数组,所以让我们继续创建文章,然后将其与当前出版物放在一起。

我只想说恭喜你已经做到了这一步,如果你只是来这里获取数据,那么你就完成了,因为在你遍历所有出版物之后,你的字典将充满你需要的所有数据!

但是,如果您想了解如何以漂亮的 Markdown 格式将其输出到您的桌面,请继续阅读 :)(我还在最后展示了线程)

如何将数据写入文件?

对于下一部分,我们不需要任何特殊的模块或包,我们需要的所有东西都内置在 python 中(多么好)。

首先,我们将创建一个名为write_to_desktop(data)的新函数,我计划将我们所有的数据写入我桌面上的 Markdown 文件,但你可以用它做任何你想做的事情,例如使用简单的文本文件或写入单独的文件驾驶。

write_to_desktop.png

我们首先在您的桌面上创建(或打开)一个文件并初始化一个空字符串,这就是最终写入文件的内容。

接下来,我们将编写两个 for 循环,第一个用于获取出版物和它的文章,第二个用于获取每篇文章的数据。如果主题标签和星号对您来说看起来很吓人,那只是为了让降价看起来不错,如果您正在写入文本文件,您可以忽略它们。

然后我们将我们的数据写入文件,我们就差不多完成了,让我们写出我们的main()函数并实际看到一些结果!

main.png

输出.png

继续运行你的脚本和 BOOM,你就完成了。恭喜......嗯,我想我确实向你们保证了奖金。让我们去提高性能🚀

奖励 - 如何使用线程提高性能

在进入本节之前,我确实想指出这可以大大提高性能,但是,如果您只从少数出版物中获取文章,您不会看到太大的变化,但是一旦您从超过 5 个出版物中获取,性能提升真正开始发挥作用。

我们这里唯一需要修改的函数是我们的main()函数,所以这不会太复杂。首先,我们需要将threading模块导入到我们的脚本中。

Threads.png

然后,我们将为每个发布创建一个新线程,所以继续调用threading.Thread将目标生成get_articles脚本,args中的所有内容都是将进入get_articles的参数。

之后,我们会将线程附加到我们的线程列表中,然后启动线程。

下一个 for 循环可能看起来有点奇怪,但我们这样做是为了让所有线程在写入数据之前完成。如果没有这个循环,我们的数据将只包含来自单个线程的数据,因为write()函数会在我们的数据准备好之前被调用。

现在尝试在顶部的字典中添加更多出版物,看看您是否注意到任何性能差异!

再次恭喜,我们正式完成了这个脚本。


结论

哇,这很多,我们讨论了什么是网络抓取,发出网络请求,抓取数据,写入文件,以及使用线程。希望您可以在工作流程的某个地方找到此脚本的用途!就个人而言,我每天早上都使用它,因为我使用黑曜石日常笔记,并且我有这个脚本将所有文章附加到笔记的底部,以便我在喝一杯好咖啡时查看☕️。请在下面评论您打算如何使用此脚本!

无论如何,我真的希望你喜欢这篇文章,如果你愿意,可以在Twitter上与我交谈!祝你有美好的一天! (代码就在下面)

zoz100077 * *

源码

import requests, datetime, threading
from bs4 import BeautifulSoup


urls = {
    "Towards Data Science": "https://towardsdatascience.com/archive/",
    "Personal Growth": "https://medium.com/personal-growth/archive/",
    "Better Programming": "https://betterprogramming.pub/archive/",
}


data = {}


def get_articles(publication, url):
    yesterday = datetime.datetime.now() - datetime.timedelta(days=1)
    date = str(yesterday.strftime("%Y/%m/%d"))
    url += date

    print(f"Checking {publication}...")

    response = requests.get(url, allow_redirects=False)

    try:
        response.raise_for_status()
    except Exception:
        print(f"Invalid URL At {url}")

    page = response.content

    soup = BeautifulSoup(page, "html.parser")
    articles = soup.find_all(
        "div",
        class_="postArticle postArticle--short js-postArticle js-trackPostPresentation js-trackPostScrolls",
    )

    if len(articles) > 0:
        print(f"Fetching Articles from {url}")

    amount_of_articles = min(3, len(articles))

    for i in range(amount_of_articles):
        title = articles[i].find("h3", class_="graf--title")

        if title is None:
            continue

        title = title.contents[0]
        article_url = articles[i].find_all("a")[3]["href"].split("?")[0]

        article = {
            "title": title,
            "article_url": article_url,
        }

        if not data.get(publication):
            data[publication] = [article]
        else:
            data[publication].append(article)


def write_to_desktop(data):
    with open("PATH_TO_DESKTOP/articles.md", "a") as file:
        out = ""

        for publication, articles in data.items():
            out += f"### ***{publication}***\n"
            for article in articles:
                out += f"#### [{article['title']}]({article['article_url']})\n\n"

            out += "---\n\n"

        file.write(out)


def main():
    threads = []

    for publication, url in urls.items():
        thread = threading.Thread(target=get_articles, args=[publication, url])
        threads.append(thread)
        thread.start()

    for thread in threads:
        thread.join()

    write_to_desktop(data)


main()
Logo

Python社区为您提供最前沿的新闻资讯和知识内容

更多推荐