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:
$ 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
Die Vorbereitung besteht aus zwei Schritten.
--Lernen Sie, wie Sie XML mit Python analysieren --Call API mit Anmeldeinformationen
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.
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".
Informationen zum Aufrufen von Hatena Blog AtomPub mit Standardauthentifizierung finden Sie unter Hatena Blog-Einstellungen> Erweiterte Einstellungen> AtomPub.
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".
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.
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)
$ 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!
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
beautifulsoup4
für die XML-Analyse zu verwenden.das ist alles.
Recommended Posts