Rufen Sie die Hatena Blog API von Python aus auf und speichern Sie Ihre Blog-Artikel einzeln auf Ihrem PC

Überblick

Als Rückblick auf den Hatena-Blog, den ich 2019 geschrieben habe, habe ich beschlossen, eine WordCloud für meinen Blog-Artikel [^ 1] zu erstellen. Nach ein wenig Recherche fand ich Artikel, der die API des Hatena-Blogs in JavaScript aufruft. Also habe ich versucht, dasselbe mit Python zu tun. Dieser Artikel fasst zusammen, was ich durch Bewegen meiner Hände gelernt habe.

[^ 1]: Aktualisieren Sie den Link nach der Veröffentlichung

Das in diesem Artikel verwendete Skript führt Folgendes aus:

Es gibt zwei Themen zu diskutieren:

  1. Vorbereitung vor dem Aufruf der API von Hatena Blog
  2. So analysieren Sie die API-Antwort (XML) von Hatena Blog

Betriebsumgebung

$ sw_vers
ProductName:	Mac OS X
ProductVersion:	10.14.4
BuildVersion:	18E226
$ python -V
Python 3.7.3  #Ich erstelle eine virtuelle Umgebung mit dem venv-Modul
$ pip list | grep requests
requests    2.22.0

Vorbereitung: Bis Sie die API von Hatena Blog aufrufen

Die Vorbereitung besteht aus zwei Schritten.

--Lernen Sie, wie Sie XML mit Python analysieren --Call API mit Anmeldeinformationen

Zunächst zur API von Hatena Blog

Hatena Blog AtomPub | http://developer.hatena.ne.jp/ja/documents/blog/apis/atom

Eine API, die das Atom Publishing Protocol (AtomPub) verwendet, ist öffentlich zugänglich.

Hatena Blog Um AtomPub verwenden zu können, muss der Client eine OAuth-Authentifizierung, eine WSSE-Authentifizierung oder eine Standardauthentifizierung durchführen.

Dieses Mal habe ich es mit ** Basisauthentifizierung ** implementiert.

Die Antwort ** wird in XML ** zurückgegeben, wie im obigen Dokument unter "Abrufen einer Liste von Blogeinträgen" beschrieben.

Vorbereitung 1. Wissen, wie man XML mit Python analysiert

Ich habe das Standardmodul ElementTree XML API verwendet.

Das Modul xml.etree.ElementTree implementiert eine einfache und effiziente API zum Parsen und Erstellen von XML-Daten.

Lassen Sie uns die Funktionsweise von XML anhand eines einfachen Beispiels überprüfen. Die gleiche Operation wurde für die Antwort des Hatena-Blogs Atom Pub durchgeführt.

practice.py


import xml.etree.ElementTree as ET

sample_xml_as_string = """<?xml version="1.0"?>
<data>
    <member name="Kumiko">
        <grade>2</grade>
        <instrument>euphonium</instrument>
    </member>
    <member name="Kanade">
        <grade>1</grade>
        <instrument>euphonium</instrument>
    </member>
    <member name="Mizore">
        <grade>3</grade>
        <instrument>oboe</instrument>
    </member>
</data>"""

root = ET.fromstring(sample_xml_as_string)

Geben Sie die Option -i [^ 2] an, wenn Sie das Skript mit dem Python-Interpreter ausführen. Diese Spezifikation ruft den interaktiven Modus auf, nachdem das Skript ausgeführt wurde (es soll vermieden werden, dass XML-Zeichenfolgen aus dem interaktiven Modus eingegeben werden müssen).

$ python -i practice.py
>>> root
<Element 'data' at 0x108ac4f48>
>>> root.tag  # <data>Stellt ein Tag dar
'data'
>>> root.attrib  # <data>Attribute auf Tags(attribute)Nicht
{}
>>> for child in root:  # <data>In Tags verschachtelt<member>Sie können das Tag herausnehmen
...     print(child.tag, child.attrib)
...
member {'name': 'Kumiko'}  #Mitglied ist ein Attribut namens Name(attribute)haben
member {'name': 'Kanade'}
member {'name': 'Mizore'}

Zusätzlich zur Anweisung "for" kann die verschachtelte Struktur von XML-Tags auch mit den Methoden "find" und "findall" [^ 3] behandelt werden.

--find: "Finde das erste untergeordnete Element mit einem bestimmten Tag" --findall: "Finde nur die direkten untergeordneten Elemente des aktuellen Elements nach Tag"

>>> #Fortsetzung
>>> someone = root.find('member')
>>> print(someone.tag, someone.attrib)
member {'name': 'Kumiko'}  #Erstes untergeordnetes Element(member)Es ist geworden
>>> members = root.findall('member')
>>> for member in members:
...     print(member.tag, member.attrib)
...
member {'name': 'Kumiko'}  #Alle untergeordneten Elemente(member)Wurde herausgenommen
member {'name': 'Kanade'}
member {'name': 'Mizore'}
>>> for member in members:
...     instrument = member.find('instrument')
...     print(instrument.text)  #Textteil zwischen Tags eingeklemmt
...
euphonium
euphonium
oboe

Dieses Mal analysierte ich die Reaktion des Blogs Atom Pub in "find" und "findall".

Vorbereitung 2. Rufen Sie die API mit Ihren Anmeldeinformationen auf

Informationen zum Aufrufen von Hatena Blog AtomPub mit Standardauthentifizierung finden Sie unter Hatena Blog-Einstellungen> Erweiterte Einstellungen> AtomPub.

hatenablog_atompub_config.png

Bei der Standardauthentifizierung wurde das Argument "auth" der Methode "get" für "Anfragen" verwendet [^ 4].

blog_entries_url = "https://blog.hatena.ne.jp/nikkie-ftnext/nikkie-ftnext.hatenablog.com/atom/entry"
user_pass_tuple = ("nikkie-ftnext", "the_api_key")
r = requests.get(blog_entries_url, auth=user_pass_tuple)
root = ET.fromstring(r.text)

Übergeben Sie die XML-formatierte Zeichenfolge "r.text" an die Methode "ElementTree.fromstring".

Analyse der API-Antwort von Hatena Blog

Wir fuhren mit der XML-Analyse fort und verglichen die tatsächliche Antwort mit "Abrufen einer Liste von Blogeinträgen" unter http://developer.hatena.ne.jp/ja/documents/blog/apis/atom.

In [20]: for child in root:  #root ist das gleiche wie im obigen Beispiel
    ...:     print(child.tag, child.attrib)  
    ...:                                                                        
{http://www.w3.org/2005/Atom}link {'rel': 'first', 'href': 'https://blog.hatena.ne.jp/nikkie-ftnext/nikkie-ftnext.hatenablog.com/atom/entry'} 
{http://www.w3.org/2005/Atom}link {'rel': 'next', 'href': 'https://blog.hatena.ne.jp/nikkie-ftnext/nikkie-ftnext.hatenablog.com/atom/entry?page=1572227492'} #Nächste Seite der Artikelliste
{http://www.w3.org/2005/Atom}title {} 
{http://www.w3.org/2005/Atom}subtitle {} 
{http://www.w3.org/2005/Atom}link {'rel': 'alternate', 'href': 'https://nikkie-ftnext.hatenablog.com/'} 
{http://www.w3.org/2005/Atom}updated {} 
{http://www.w3.org/2005/Atom}author {} 
{http://www.w3.org/2005/Atom}generator {'uri': 'https://blog.hatena.ne.jp/', 'version': '3977fa1b6c9f31b5eab4610099c62851'} 
{http://www.w3.org/2005/Atom}id {} 
{http://www.w3.org/2005/Atom}entry {} #Einzelne Artikel
{http://www.w3.org/2005/Atom}entry {} 
{http://www.w3.org/2005/Atom}entry {} 
{http://www.w3.org/2005/Atom}entry {} 
{http://www.w3.org/2005/Atom}entry {} 
{http://www.w3.org/2005/Atom}entry {} 
{http://www.w3.org/2005/Atom}entry {} 
{http://www.w3.org/2005/Atom}entry {} 
{http://www.w3.org/2005/Atom}entry {} 
{http://www.w3.org/2005/Atom}entry {} 

Wir werden uns auch einzelne Artikel ansehen (Tags sind Elemente des Eintrags).

>>> for item in child:  #Kind enthält einen Artikel (der Rest des für)
...     print(item.tag, item.attrib)
...
{http://www.w3.org/2005/Atom}id {}
{http://www.w3.org/2005/Atom}link {'rel': 'edit', 'href': 'https://blog.hatena.ne.jp/nikkie-ftnext/nikkie-ftnext.hatenablog.com/atom/entry/26006613457877510'}
{http://www.w3.org/2005/Atom}link {'rel': 'alternate', 'type': 'text/html', 'href': 'https://nikkie-ftnext.hatenablog.com/entry/rejectpy2019-plan-step0'}
{http://www.w3.org/2005/Atom}author {}
{http://www.w3.org/2005/Atom}title {}  #Titel (Ziel von WordCloud)
{http://www.w3.org/2005/Atom}updated {}
{http://www.w3.org/2005/Atom}published {}  #Hinweis: Auch im Entwurf gespeichert
{http://www.w3.org/2007/app}edited {}
{http://www.w3.org/2005/Atom}summary {'type': 'text'}
{http://www.w3.org/2005/Atom}content {'type': 'text/x-markdown'}  #Text
{http://www.hatena.ne.jp/info/xmlns#}formatted-content {'type': 'text/html'}
{http://www.w3.org/2005/Atom}category {'term': 'Sprecherbericht'}
{http://www.w3.org/2007/app}control {}  #Enthält Informationen darüber, ob das untergeordnete Element ein Entwurf ist oder nicht

Ich möchte WordCloud aus veröffentlichten Artikeln erstellen. Überprüfen Sie daher den Entwurf des untergeordneten Elements von "{http://www.w3.org/2007/app} control" (wenn ja, handelt es sich um einen unveröffentlichten Entwurf, der nicht anwendbar ist.) ..

Der für WordCloud verwendete Text wurde verwendet, indem der Titel und der Text verbunden wurden.

Der ganze Code

Veröffentlicht im folgenden Repository: https://github.com/ftnext/hatenablog-atompub-python

main.py


import argparse
from datetime import datetime, timedelta, timezone
import os
from pathlib import Path
import xml.etree.ElementTree as ET

import requests


def load_credentials(username):
    """Gibt die für den Hatena-API-Zugriff erforderlichen Anmeldeinformationen im Tapple-Format zurück"""
    auth_token = os.getenv("HATENA_BLOG_ATOMPUB_KEY")
    message = "Umgebungsvariable`HATENA_BLOG_ATOMPUB_KEY`Bitte setzen Sie den API-Schlüssel von AtomPub auf"
    assert auth_token, message
    return (username, auth_token)


def retrieve_hatena_blog_entries(blog_entries_uri, user_pass_tuple):
    """Erhalten Sie Zugriff auf die Hatena Blog-API und geben Sie XML zurück, das die Artikelliste als Zeichenfolge darstellt"""
    r = requests.get(blog_entries_uri, auth=user_pass_tuple)
    return r.text


def select_elements_of_tag(xml_root, tag):
    """Analysiert das zurückgegebene XML und gibt alle untergeordneten Elemente mit dem angegebenen Tag zurück"""
    return xml_root.findall(tag)


def return_next_entry_list_uri(links):
    """Gibt den Endpunkt der folgenden Blog-Artikelliste zurück"""
    for link in links:
        if link.attrib["rel"] == "next":
            return link.attrib["href"]


def is_draft(entry):
    """Bestimmen Sie, ob ein Blog-Beitrag ein Entwurf ist"""
    draft_status = (
        entry.find("{http://www.w3.org/2007/app}control")
        .find("{http://www.w3.org/2007/app}draft")
        .text
    )
    return draft_status == "yes"


def return_published_date(entry):
    """Gibt das Veröffentlichungsdatum des Blogposts zurück

Es war eine Spezifikation, die auch im Falle eines Entwurfs zurückgegeben wurde
    """
    publish_date_str = entry.find(
        "{http://www.w3.org/2005/Atom}published"
    ).text
    return datetime.fromisoformat(publish_date_str)


def is_in_period(datetime_, start, end):
    """Stellen Sie fest, ob das angegebene Datum und die angegebene Uhrzeit im Zeitraum von Anfang bis Ende enthalten sind"""
    return start <= datetime_ < end


def return_id(entry):
    """Gibt den ID-Teil zurück, der in der URI des Blogs enthalten ist"""
    link = entry.find("{http://www.w3.org/2005/Atom}link")
    uri = link.attrib["href"]
    return uri.split("/")[-1]


def return_contents(entry):
    """Verbinden Sie den Blog-Titel und den Text und geben Sie ihn zurück"""
    title = entry.find("{http://www.w3.org/2005/Atom}title").text
    content = entry.find("{http://www.w3.org/2005/Atom}content").text
    return f"{title}。\n\n{content}"


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("hatena_id")
    parser.add_argument("blog_domain")
    parser.add_argument("target_year", type=int)
    parser.add_argument("--output", type=Path)
    args = parser.parse_args()

    hatena_id = args.hatena_id
    blog_domain = args.blog_domain
    target_year = args.target_year
    output_path = args.output if args.output else Path("output")

    user_pass_tuple = load_credentials(hatena_id)

    blog_entries_uri = (
        f"https://blog.hatena.ne.jp/{hatena_id}/{blog_domain}/atom/entry"
    )

    jst_tz = timezone(timedelta(seconds=9 * 60 * 60))
    date_range_start = datetime(target_year, 1, 1, tzinfo=jst_tz)
    date_range_end = datetime(target_year + 1, 1, 1, tzinfo=jst_tz)

    oldest_published_date = datetime.now(jst_tz)
    target_entries = []

    while date_range_start <= oldest_published_date:
        entries_xml = retrieve_hatena_blog_entries(
            blog_entries_uri, user_pass_tuple
        )
        root = ET.fromstring(entries_xml)

        links = select_elements_of_tag(
            root, "{http://www.w3.org/2005/Atom}link"
        )
        blog_entries_uri = return_next_entry_list_uri(links)

        entries = select_elements_of_tag(
            root, "{http://www.w3.org/2005/Atom}entry"
        )
        for entry in entries:
            if is_draft(entry):
                continue
            oldest_published_date = return_published_date(entry)
            if is_in_period(
                oldest_published_date, date_range_start, date_range_end
            ):
                target_entries.append(entry)
        print(f"{oldest_published_date}Holen Sie sich Artikel bis (alle{len(target_entries)}Fall)")

    output_path.mkdir(parents=True, exist_ok=True)

    for entry in target_entries:
        id_ = return_id(entry)
        file_path = output_path / f"{id_}.txt"
        contents = return_contents(entry)
        with open(file_path, "w") as fout:
            fout.write(contents)

Ausführungsbeispiel

$ python main.py nikkie-ftnext nikkie-ftnext.hatenablog.com 2019 --output output/2019
2019-10-30 11:25:23+09:Erhalten Sie Artikel bis zu 00 (9 insgesamt)
2019-06-13 10:18:36+09:Erhalten Sie Artikel bis zu 00 (insgesamt 18)
2019-03-30 13:52:19+09:Erhalten Sie Artikel bis zu 00 (insgesamt 27)
2018-12-23 10:24:06+09:Erhalten Sie Artikel bis zu 00 (insgesamt 32)
# -> output/Unter 2019 werden 32 Textdateien erstellt (Inhalte sind Blogs, die ich geschrieben habe)

Auf diese Weise konnte ich den Blog-Artikel, den ich geschrieben habe, aus dem Hatena-Blog AtomPub erhalten!

Senden

Wenn ich dasselbe wiederhole, möchte ich Folgendes untersuchen und einbeziehen:

――Es scheint, dass Sie nur Ihren eigenen Blog von Atom Pub erhalten können ――Wenn Sie auf die Blogs anderer Personen abzielen möchten, müssen Sie wahrscheinlich ein Blog-Mitglied werden

das ist alles.

Recommended Posts

Rufen Sie die Hatena Blog API von Python aus auf und speichern Sie Ihre Blog-Artikel einzeln auf Ihrem PC
Holen Sie sich Artikelbesuche und Likes mit Qiita API + Python
[Python] Ruft den Gesetzestext aus der e-GOV-Gesetz-API ab
Rufen Sie Ihr eigenes Python-Modul aus dem ROS-Paket auf
Holen Sie sich Ihre Herzfrequenz von der Fitbit-API in Python!
So geben Sie die Anzahl der ANSICHTEN, Likes und Bestände von Artikeln aus, die in Qiita an CSV gesendet wurden (erstellt mit "Python + Qiita API v2")
Rufen Sie die API mit python3 auf.
Installieren Sie mecab auf dem gemeinsam genutzten Sakura-Server und rufen Sie es von Python aus auf
So erhalten Sie mithilfe der Mastodon-API Follower und Follower von Python
Der Weg zur Installation von Python und Flask auf einem Offline-PC
Betreiben Sie Firefox mit Selen aus Python und speichern Sie die Bildschirmaufnahme
Die Geschichte von Python und die Geschichte von NaN
Existenz aus Sicht von Python
Verwenden Sie die Flickr-API von Python
Rufen Sie C / C ++ von Python auf dem Mac auf
[Python] Senden Sie das von der Webkamera aufgenommene Bild an den Server und speichern Sie es
Notizen vom Anfang von Python 1 lernen
Ich bin auf die Hatena Keyword API gestoßen
Notizen vom Anfang von Python 2 lernen
Speichern Sie automatisch Bilder Ihrer Lieblingsfiguren aus der Google Bildsuche mit Python
Lassen Sie uns mit Python Receive spielen und den Text des Eingabeformulars speichern / anzeigen
[C-Sprache] Achten Sie auf die Kombination aus Puffer-API und nicht pufferndem Systemaufruf
Deep Learning von Grund auf neu Die Theorie und Implementierung des mit Python erlernten Deep Learning Kapitel 3
Installieren Sie die Python-API des automatischen Fahrsimulators LGSVL und führen Sie das Beispielprogramm aus
[Python] Summiert automatisch die Gesamtzahl der von Qiita mithilfe der API veröffentlichten Artikel
Holen Sie sich den Inhalt von Git Diff aus Python
Zusammenfassung der Unterschiede zwischen PHP und Python
Die Antwort von "1/2" unterscheidet sich zwischen Python2 und 3
Angeben des Bereichs von Ruby- und Python-Arrays
Vergleichen Sie die Geschwindigkeit von Python Append und Map
Verknüpfung von PHP und Python von Grund auf auf Laravel
Zum Zeitpunkt des Python-Updates mit Ubuntu
Berücksichtigung der Stärken und Schwächen von Python
Rufen Sie Polly aus dem AWS SDK für Python auf
Versuchen Sie, direkt von Python 3 aus auf die YQL-API zuzugreifen
[Python3] Machen Sie einen Screenshot einer Webseite auf dem Server und schneiden Sie sie weiter zu
Erstellen Sie einen API-Server, um den Betrieb der Front-Implementierung mit Python3 und Flask zu überprüfen
Extrahieren Sie Bilder und Tabellen mit Python aus PDF, um die Berichtslast zu verringern
Ich habe versucht, das Artikel-Update des Livedoor-Blogs mit Python und Selen zu automatisieren.
Ich habe die Geschwindigkeit der Referenz des Pythons in der Liste und die Referenz der Wörterbucheinbeziehung aus der In-Liste verglichen.
Lassen Sie uns über das Wetter in Ihrer Lieblingsgegend von Yahoo Wetter auf LINE informiert werden!
[Python] Speichern Sie das Ergebnis des Web-Scrapings der Mercari-Produktseite in Google Colab in einer Google-Tabelle und zeigen Sie auch das Produktbild an.