編集の要約なし
>夜泣き 編集の要約なし |
>夜泣き 編集の要約なし |
||
1行目: | 1行目: | ||
とりあえず取り急ぎ。バグ報告は[[利用者・トーク:夜泣き]] | とりあえず取り急ぎ。バグ報告は[[利用者・トーク:夜泣き]] | ||
== コード == | == コード == | ||
44行目: | 40行目: | ||
from bs4 import BeautifulSoup as bs4 | from bs4 import BeautifulSoup as bs4 | ||
from typing import Final | from typing import Final | ||
from typing import Optional | |||
from urllib.parse import quote | from urllib.parse import quote | ||
import warnings | |||
#定数・設定類 | |||
##おそらくはツイートの内容によってMarkupResemblesLocatorWarningが吐き出されることがあるので無効化 | |||
warnings.simplefilter('ignore') | |||
##nitterのインスタンス | ##nitterのインスタンス | ||
##生きているのはhttps://github.com/zedeus/nitter/wiki/Instancesで確認 | ##生きているのはhttps://github.com/zedeus/nitter/wiki/Instancesで確認 | ||
74行目: | 75行目: | ||
Limit_request: Final[int] = 5 | Limit_request: Final[int] = 5 | ||
## | ##HTTPリクエスト失敗時にさらに追加する待機時間 | ||
Waittime_error: Final[int] = | Waittime_error: Final[int] = 4 | ||
## | ##HTTPリクエスト成功失敗関わらず待機時間 | ||
##1秒待つだけで行儀がいいクローラーだそうなので既定では1秒 | |||
##しかし日本のポリホーモは1秒待っていても捕まえてくるので注意 | |||
##https://ja.wikipedia.org/wiki/?curid=2187212 | |||
Waittime: Final[int] = 1 | |||
##nitterのURLのドメインとユーザーネーム部分の接続部品 | ##nitterのURLのドメインとユーザーネーム部分の接続部品 | ||
112行目: | 116行目: | ||
##失敗かどうかは呼出側で要判定 | ##失敗かどうかは呼出側で要判定 | ||
def request_onetime(url: Final[str]) -> requests.models.Response: | def request_onetime(url: Final[str]) -> requests.models.Response: | ||
res =requests.get(url, timeout=Request_timeout, headers=header,allow_redirects=False) | |||
sleep(Waittime) ##DOS対策で待つ | |||
return res | |||
##HTTP接続を再試行回数まで試します | ##HTTP接続を再試行回数まで試します | ||
118行目: | 124行目: | ||
##接続失敗が何度も起きるとNoneを返します | ##接続失敗が何度も起きるとNoneを返します | ||
##呼出側で要None判定 | ##呼出側で要None判定 | ||
def request(url: Final[str]) -> requests.models.Response: | def request(url: Final[str]) -> Optional[requests.models.Response]: | ||
counter = 1 | counter = 1 ##リクエスト挑戦回数を記録 | ||
while True: | while True: | ||
try: | try: | ||
res = request_onetime(url) | res = request_onetime(url) ##リクエスト | ||
res.raise_for_status() | res.raise_for_status() ##HTTPステータスコードが200番台以外でエラー発生 | ||
except requests.exceptions.RequestException as e: | except requests.exceptions.RequestException as e: | ||
print(url + 'への通信失敗ナリ ' + str(counter) + '/' + str(Limit_request) + '回') | |||
if counter < Limit_request: ##エラー発生時上限まで再挑戦 | |||
counter += 1 | counter += 1 ##現在の試行回数1回増やす | ||
sleep(Waittime_error) | sleep(Waittime_error) ##失敗時は長めに待つ | ||
else: | else: | ||
return None | return None ##失敗したらNone返却し呼出側で対処してもらう | ||
else: | else: | ||
return res | return res ##リクエストの結果返す | ||
##URLの最後にスラッシュ付いていなければ付ける | ##URLの最後にスラッシュ付いていなければ付ける | ||
159行目: | 165行目: | ||
res = request_onetime(Nitterinstance) ##リクエスト | res = request_onetime(Nitterinstance) ##リクエスト | ||
res.raise_for_status() ##HTTPステータスコードが200番台以外でエラー発生 | res.raise_for_status() ##HTTPステータスコードが200番台以外でエラー発生 | ||
except requests.exceptions.RequestException as e: ##エラー発生時は終了 | except requests.exceptions.RequestException as e: ##エラー発生時は終了 | ||
print('インスタンスが死んでますを') | print('インスタンスが死んでますを') | ||
177行目: | 182行目: | ||
if res is None : ##リクエスト失敗判定 | if res is None : ##リクエスト失敗判定 | ||
fail() | fail() | ||
soup = bs4(res.text, 'html.parser') ##beautifulsoupでレスポンス解析 | soup = bs4(res.text, 'html.parser') ##beautifulsoupでレスポンス解析 | ||
if soup.title == Nitter_error_title: ##タイトルがエラーでないか判定 | if soup.title == Nitter_error_title: ##タイトルがエラーでないか判定 | ||
184行目: | 188行目: | ||
print("最終的に出会ったのが@" + account_str + "だった。") | print("最終的に出会ったのが@" + account_str + "だった。") | ||
return account_str ##成功時アカウント名返す | return account_str ##成功時アカウント名返す | ||
##検索クエリを取得 | ##検索クエリを取得 | ||
def get_query(name: Final[str]) -> requests.models.Response: | def get_query(name: Final[str]) -> requests.models.Response: | ||
199行目: | 203行目: | ||
if res is None : ##リクエスト失敗判定 | if res is None : ##リクエスト失敗判定 | ||
fail() | fail() | ||
soup = bs4(res.text, 'html.parser') ##beautifulsoupでレスポンス解析 | soup = bs4(res.text, 'html.parser') ##beautifulsoupでレスポンス解析 | ||
##1件もないときはHTMLにtimeline-noneがあるから判定 | ##1件もないときはHTMLにtimeline-noneがあるから判定 | ||
245行目: | 248行目: | ||
continue | continue | ||
tweet_url = Twitterurl + re.sub('#[^#]*$','',doublesoup.find(class_='tweet-link').get('href')) ##ツイートのURL作成 | tweet_url = Twitterurl + re.sub('#[^#]*$','',doublesoup.find(class_='tweet-link').get('href')) ##ツイートのURL作成 | ||
archived_tweet_url = | archived_tweet_url = callinshowlinkurl(tweet_url) ##ツイートURLをテンプレートCallinShowlinkに変化 | ||
tweet_content = doublesoup.find(class_='tweet-content media-body') ##ツイートの中身だけ取り出す | tweet_content = doublesoup.find(class_='tweet-content media-body') ##ツイートの中身だけ取り出す | ||
triplesoup = bs4(str(tweet_content) , 'html.parser') ##URLの魚拓化のためにさらに解析 | triplesoup = bs4(str(tweet_content) , 'html.parser') ##URLの魚拓化のためにさらに解析 | ||
263行目: | 266行目: | ||
urls_in_tweet = soup.find_all('a') | urls_in_tweet = soup.find_all('a') | ||
for url in urls_in_tweet: | for url in urls_in_tweet: | ||
if not re.match('^https?://',url.get('href')) is None: | if not re.match('^https?://',url.get('href')) is None: ##先頭にhttpが付いていない物はハッシュタグの検索ページへのリンクなので処理しない | ||
newstr = soup.new_string(archiveurl(url.get('href'),url.text)) ##テンプレートArchiveの文字列作成 | newstr = soup.new_string(archiveurl(url.get('href'),url.text)) ##テンプレートArchiveの文字列作成 | ||
url.replace_with(newstr) ##テンプレートArchiveに変化 | url.replace_with(newstr) ##テンプレートArchiveに変化 | ||
270行目: | 273行目: | ||
def archiveurl(url: Final[str],text: Final[str]) -> str: | def archiveurl(url: Final[str],text: Final[str]) -> str: | ||
return '{{Archive|1=' + url + '|2=' + archive(url) + '|3=' + text + '}}' ##テンプレートArchiveの文字列返す | return '{{Archive|1=' + url + '|2=' + archive(url) + '|3=' + text + '}}' ##テンプレートArchiveの文字列返す | ||
#URLをテンプレートCallinShowlinkに変化させる | |||
def callinshowlinkurl(url: Final[str]) -> str: | |||
return '{{CallinShowLink|1=' + url + '|2=' + archive(url) + '}}' ##テンプレートCallinShowlinkの文字列返す | |||
##URLから魚拓返す | ##URLから魚拓返す | ||
def archive(url: Final[str]) -> str: | def archive(url: Final[str]) -> str: | ||
archive_url = Archivetodaystandard + url | archive_url = Archivetodaystandard + url ##wikiに載せるとき用URLで失敗するとこのままhttps://archive.ph/https://xxxxxxxxの形で返される | ||
res = request(Archivetoday + url) | res = request(Archivetoday + url) ##アクセス用URL使って結果を取得 | ||
if res is None : ##魚拓接続失敗時処理 | if res is None : ##魚拓接続失敗時処理 | ||
print(archive_url + 'にアクセス失敗ナリ。出力されるテキストにはそのまま記載されるナリ。') | print(archive_url + 'にアクセス失敗ナリ。出力されるテキストにはそのまま記載されるナリ。') | ||
else: | else: | ||
soup = bs4(res.text, 'html.parser') ##beautifulsoupでレスポンス解析 | soup = bs4(res.text, 'html.parser') ##beautifulsoupでレスポンス解析 | ||
content = soup.find(id="CONTENT") | content = soup.find(id="CONTENT") ##archive.todayの魚拓一覧ページの中身だけ取得 | ||
if content[:len(Noarchive)] == Noarchive: | if content.get_text()[:len(Noarchive)] == Noarchive: ##魚拓があるかないか判定 | ||
print(url + "の魚拓がない。これはいけない。") | print(url + "の魚拓がない。これはいけない。") | ||
else: | else: | ||
doublesoup = bs4( | doublesoup = bs4(str(content), 'html.parser') ##beautifulsoupでレスポンス解析 | ||
archive_url = | archive_url = doublesoup.find('a').get('href').replace(Archivetoday,Archivetodaystandard) | ||
return archive_url | return archive_url | ||
295行目: | 301行目: | ||
if showmore.text != Newest: ##前ページへのリンクではないか判定 | if showmore.text != Newest: ##前ページへのリンクではないか判定 | ||
newurl = Nitterinstance + Search + showmore.a.get('href') ##直下のaタグのhrefの中身取ってURL頭部分と合体 | newurl = Nitterinstance + Search + showmore.a.get('href') ##直下のaタグのhrefの中身取ってURL頭部分と合体 | ||
res = request(newurl) | res = request(newurl) ##接続してHTML取ってくる | ||
if res is None: | if res is None: | ||
fail() | fail() | ||
newsoup = bs4(res.text, 'html.parser') ##beautifulsoupでレスポンス解析 | |||
newsoup = bs4(res.text, 'html.parser') | if newsoup.find(class_="timeline-end") is None: ##ツイートの終端ではtimeline-endだけのページになるので判定 | ||
if newsoup.find(class_="timeline-end") is None: | |||
print(res.url + 'に移動しますを') | print(res.url + 'に移動しますを') | ||
return res | return res ##まだ残りツイートがあるのでページを返して再度ツイート本文収集 | ||
else: | else: | ||
print("急に残りツイートが無くなったな終了するか") | print("急に残りツイートが無くなったな終了するか") | ||
312行目: | 317行目: | ||
txt_data = '' ##出力するデータ | txt_data = '' ##出力するデータ | ||
limitcount = 0 ##記録数 | limitcount = 0 ##記録数 | ||
krsw=False | krsw=False ##コマンドライン引数があるかどうかのフラグ | ||
##コマンドライン引数取得 | |||
if len(sys.argv) > 1 and sys.argv[1] == 'krsw': | if len(sys.argv) > 1 and sys.argv[1] == 'krsw': | ||
krsw=True | krsw=True | ||
349行目: | 355行目: | ||
20件での実行例。 | 20件での実行例。 | ||
{|class="wikitable" style="text-align: left;" | {|class="wikitable" style="text-align: left;" | ||
!{{ | !{{CallinShowLink|1=https://twitter.com/CallinShow/status/1537795802302918656|2=https://archive.ph/AuE5x}} | ||
|- | |- | ||
| | | | ||
391行目: | 362行目: | ||
在野の声をSNSを使って行っていくしかない。 | 在野の声をSNSを使って行っていくしかない。 | ||
|- | |- | ||
!{{ | !{{CallinShowLink|1=https://twitter.com/CallinShow/status/1537796046780497920|2=https://archive.ph/OlZiU}} | ||
|- | |- | ||
| | | | ||
398行目: | 369行目: | ||
全てを変えるんだ。 | 全てを変えるんだ。 | ||
|- | |- | ||
!{{ | !{{CallinShowLink|1=https://twitter.com/CallinShow/status/1537796546263416832|2=https://archive.ph/dZjIg}} | ||
|- | |- | ||
| | | | ||
410行目: | 381行目: | ||
何でこんなに若い人が苦しまないといけないんだ。 | 何でこんなに若い人が苦しまないといけないんだ。 | ||
|- | |- | ||
!{{ | !{{CallinShowLink|1=https://twitter.com/CallinShow/status/1537796983578333184|2=https://archive.ph/e1YIX}} | ||
|- | |- | ||
| | | | ||
417行目: | 388行目: | ||
ガチャのチケットもってるのに使わない手はないだろ。 | ガチャのチケットもってるのに使わない手はないだろ。 | ||
|- | |- | ||
!{{ | !{{CallinShowLink|1=https://twitter.com/CallinShow/status/1537797605308395520|2=https://archive.ph/sTiWk}} | ||
|- | |- | ||
| | | | ||
👍 | 👍 | ||
|- | |- | ||
!{{ | !{{CallinShowLink|1=https://twitter.com/CallinShow/status/1537798386619133952|2=https://archive.ph/VECnL}} | ||
|- | |- | ||
| | | | ||
頼むよ!君にかかってる。 | 頼むよ!君にかかってる。 | ||
|- | |- | ||
!{{ | !{{CallinShowLink|1=https://twitter.com/CallinShow/status/1537799090326863872|2=https://archive.ph/OIfjs}} | ||
|- | |- | ||
| | | | ||
438行目: | 409行目: | ||
全ては見過ごされてるんだよ。 | 全ては見過ごされてるんだよ。 | ||
|- | |- | ||
!{{ | !{{CallinShowLink|1=https://twitter.com/CallinShow/status/1537803303752388608|2=https://archive.ph/M0BdR}} | ||
|- | |- | ||
| | | | ||
447行目: | 418行目: | ||
引用リスペクト 梅野源治選手 | 引用リスペクト 梅野源治選手 | ||
|- | |- | ||
!{{ | !{{CallinShowLink|1=https://twitter.com/CallinShow/status/1538070416488792064|2=https://archive.ph/9d43q}} | ||
|- | |- | ||
| | | | ||
454行目: | 425行目: | ||
貴重な意見ありがとう。 | 貴重な意見ありがとう。 | ||
|- | |- | ||
!{{ | !{{CallinShowLink|1=https://twitter.com/CallinShow/status/1538070983827165186|2=https://archive.ph/nz7k9}} | ||
|- | |- | ||
| | | | ||
466行目: | 437行目: | ||
若い人たちで国を作るんだ。 | 若い人たちで国を作るんだ。 | ||
|- | |- | ||
!{{ | !{{CallinShowLink|1=https://twitter.com/CallinShow/status/1538071752802467840|2=https://archive.ph/ON3Hx}} | ||
|- | |- | ||
| | | | ||
487行目: | 458行目: | ||
こんな人今までいたか。 | こんな人今までいたか。 | ||
|- | |- | ||
!{{ | !{{CallinShowLink|1=https://twitter.com/CallinShow/status/1538072997734793216|2=https://archive.ph/AoMxP}} | ||
|- | |- | ||
| | | | ||
504行目: | 475行目: | ||
その一心でしかないと思う。 | その一心でしかないと思う。 | ||
|- | |- | ||
!{{ | !{{CallinShowLink|1=https://twitter.com/CallinShow/status/1538073614540754944|2=https://archive.ph/SYHKj}} | ||
|- | |- | ||
| | | | ||
517行目: | 488行目: | ||
いいじゃないか、澱んだ永田町が少しは澄んでくるんじゃないか。 | いいじゃないか、澱んだ永田町が少しは澄んでくるんじゃないか。 | ||
|- | |- | ||
!{{ | !{{CallinShowLink|1=https://twitter.com/CallinShow/status/1538074524721491968|2=https://archive.ph/vqHml}} | ||
|- | |- | ||
| | | | ||
524行目: | 495行目: | ||
澱んだ街ごと食い荒らすんだ。 | 澱んだ街ごと食い荒らすんだ。 | ||
|- | |- | ||
!{{ | !{{CallinShowLink|1=https://twitter.com/CallinShow/status/1538582552423780352|2=https://archive.ph/https://twitter.com/CallinShow/status/1538582552423780352}} | ||
|- | |- | ||
| | | | ||
530行目: | 501行目: | ||
<nowiki>#</nowiki>マシュマロを投げ合おう<br> | <nowiki>#</nowiki>マシュマロを投げ合おう<br> | ||
{{Archive|1=https://marshmallow-qa.com/apt/21e00e4b-e468-42e9-bd14-44c2bf9b37d0|2=https://archive.ph/https://marshmallow-qa.com/apt/21e00e4b-e468-42e9-bd14-44c2bf9b37d0|3=marshmallow-qa.com/apt/21e00…}} | {{Archive|1=https://marshmallow-qa.com/apt/21e00e4b-e468-42e9-bd14-44c2bf9b37d0|2=https://archive.ph/https://marshmallow-qa.com/apt/21e00e4b-e468-42e9-bd14-44c2bf9b37d0|3=marshmallow-qa.com/apt/21e00…}} | ||
|- | |||
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1539223436337516548|2=https://archive.ph/tBmP8}} | |||
|- | |||
| | |||
{{Archive|1=https://www.fsa.go.jp/sesc/news/c_2022/2022/20220621-3.html|2=https://archive.ph/EyPMc|3=fsa.go.jp/sesc/news/c_2022/2…}}<br> | |||
<br> | |||
エクシアさん、こういった問題について、どうお考えなんですかね。 | |||
|- | |||
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1539223981106302976|2=https://archive.ph/6XxFe}} | |||
|- | |||
| | |||
エクシアの関戸さんの経営されている会社等のご存じの方いらっしゃいましたらご連絡お願い申し上げます。 | |||
|- | |||
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1539224957829672960|2=https://archive.ph/iJxRd}} | |||
|- | |||
| | |||
そちらは法人名でしょうか? | |||
|- | |||
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1539237355701379075|2=https://archive.ph/https://twitter.com/CallinShow/status/1539237355701379075}} | |||
|- | |||
| | |||
菊地さんは虎ノ門ヒルズいくらで買ったんだろ。<br> | |||
キャッシュで | |||
|- | |||
!{{CallinShowLink|1=https://twitter.com/CallinShow/status/1539237797281816576|2=https://archive.ph/https://twitter.com/CallinShow/status/1539237797281816576}} | |||
|- | |||
| | |||
仮に3億だとして、これを買うのに、所得としては6億くらいの報酬を得ないとダメだよな。 | |||
|- | |- | ||
|} | |} |