2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Qiitaに自動投稿と画像アップロード

Last updated at Posted at 2023-07-16

概要

Python requestsを使って,この記事とその画像をqiitaに自動投稿する.

ソースコード(参考用): https://github.com/6e5d/qiita_post.

自動投稿モデル

緑色ノード = クライエント保存情報.

A. regexなどを利用してローカルファイルリンクを検出.

B. 保存したS3 URLと更新時間(timestamp)より新しい,またはS3に存在しなかった画像を集める.

C. (下記参照)

D. ローカルファイルリンクをS3 URLに置き換える.

E. (下記参照)

C. 画像アップロード

注意: 公式APIに入られなかったので,破壊的な変更が行われる可能性がある.

参考: https://qiita.com/yuku_t/items/40b7daf018d3dab48974

Qiitaの投稿には2M文字上限があるので(参考: https://qiita.com/misohagi/items/39c4f9921fdc009817b5), HTMLの中に直接画像エンコード(base64など)は向いてない. qiita.comは画像などのサーバーではなく, amazon S3をアップロードして生成したリンクを埋め込むということです.

まず,アップロードしたいの画像情報をqiitaのサーバーに提出した後, qiita返信からS3サーバーの目標URLなどを抽出. 具体的には,画像タイプ,名前,サイズを含むjsonデータを https://qiita.com/api/upload/policiesにPOST:

# stage1: qiita.comと通信
def stage1(token, img):
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {token}",
}
data = {"image": {
"content_type": "image/png",
"name": "test.png",
"size": len(img),
}}
resp = requests.post(
"https://qiita.com/api/upload/policies",
data = json.dumps(data),
headers = headers,
)
assert resp.status_code == 200
content = resp.content
resp.close()
j = json.loads(content.decode())
return j

返信datajj["form"]部分はmultipart/form-data filesを転換し (requestsを使えば,自動的にboundaryなどを生成する), 画像データを加えてj["upload_url"]にPOST. 生成されたurl(Location)は返信Headersに含まれる:

# stage2: S3と通信
def stage2(j, img):
url = j["upload_url"]
files = j["form"]
for name, value in files.items():
files[name] = (None, value)
files["file"] = ("test.png", img, "image/png")
resp = requests.post(url, files = files)
# print(resp.status_code)
headers = resp.headers
resp.close()
loc = headers["Location"]
return loc

E. Markdownファイルを投稿

公式API: https://qiita.com/api/v2/docs#投稿

参考: https://qiita.com/kai_kou/items/663d3f7bbc4da4ccf62d.

  • 投稿本文フォーマットは2種類Markdown(body)とHTML(rendered_body)がある.

  • POST=投稿,PATCH=更新,DELETE=削除

  • 返信bodyから投稿URLを抽出して保存(更新にはURLが必要)

def post(prev_url, text, title, tags):
data = {
"body": text,
"private": True,
"title": title,
"tags": [{"name": tag, "versions": []} for tag in tags]
}
import requests
token = open("token.txt").read().strip()
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {token}",
}
if prev_url == None:
print(f"new post")
resp = requests.post(
"https://qiita.com/api/v2/items",
headers = headers,
data = json.dumps(data),
)
else:
print(f"update {prev_url}")
resp = requests.patch(
prev_url,
headers = headers,
data = json.dumps(data),
)
print(resp.status_code)
print(resp.headers)
j = json.loads(resp.content.decode())
# 毎回セーブするけど,記事IDは更新後も変更されない
with open("qiita/prev_url.txt", "w") as f:
print(j["url"], file = f)

画像の更新と削除について

画像削除API(ドキュメントなし)は複雑で不安定ですが, 100Mストレージは余裕があると思うので,いまのところ実装しない方がいい. クライエント側から管理するより, QiitaにはGarbage Collection機能 (リンクされていない画像は一定時間後に自動またはまとめて削除) を実現することを望む.

230916 更新

今は(Makefileみたいな)epoch比較ではなく, binaryファイルがVCSで管理されて安定的なハッシュ比較を実装している.

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?