「利用者:夜泣き/スクリプト」の版間の差分
< 利用者:夜泣き
ナビゲーションに移動
検索に移動
>夜泣き 編集の要約なし |
>夜泣き 編集の要約なし |
||
1行目: | 1行目: | ||
とりあえず取り急ぎ。バグ報告は[[利用者・トーク:夜泣き]] | |||
== コード == | |||
<syntaxhighlight lang="python3" line> | |||
#!/usr/bin/env python3 | |||
< | |||
#!/usr/bin/env | |||
''' | ''' | ||
11行目: | 8行目: | ||
前開発者との出会いに感謝 | 前開発者との出会いに感謝 | ||
〜〜〜〜〜〜〜〜〜〜〜〜〜【使い方】〜〜〜〜〜〜〜〜〜〜〜〜〜 | |||
・terminalに $ python3 (ファイル名) で作動します | |||
・定数類は状況に応じて変えてください | |||
・$ python3 (ファイル名) krsw コマンドライン引数をkrswとつけると自動モードです | |||
・自動モードではユーザーは降臨ショー、クエリはなし、取得上限まで自動です | |||
・つまりユーザー入力が要りません | |||
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜 | |||
ーーーーーーーーーーーーー【注意点】ーーーーーーーーーーーーー | |||
・環境は玉葱前提です。 | |||
・Whonix-Workstationで動作確認済 | |||
・bs4はインストールしないと標準で入ってません | |||
・requestsも環境によっては入っていないかもしれない | |||
・$ pip install bs4 requests | |||
・pipも入っていなければ$ sudo apt install pip | |||
・バグ報告はhttps://krsw-wiki.org/wiki/?curid=15799 | |||
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー | |||
''' | |||
#インポート類 | #インポート類 | ||
import | import sys | ||
import codecs | import codecs | ||
import re | import re | ||
179行目: | 53行目: | ||
##記事の文章に使うのはクリアネット | ##記事の文章に使うのはクリアネット | ||
Archivetodaystandard = 'https://archive.ph/' | Archivetodaystandard = 'https://archive.ph/' | ||
##twitterのURL | |||
Twitterurl = 'https://twitter.com/' | |||
##降臨ショーのユーザーネーム | ##降臨ショーのユーザーネーム | ||
187行目: | 64行目: | ||
##取得するツイート数の上限 | ##取得するツイート数の上限 | ||
Limit_tweet: Final[int] = | Limit_tweet: Final[int] = 100 | ||
##HTTPリクエスト失敗時の再試行回数 | ##HTTPリクエスト失敗時の再試行回数 | ||
193行目: | 70行目: | ||
##HTTPリクエスト失敗時の待機時間 | ##HTTPリクエスト失敗時の待機時間 | ||
Waittime_error: Final[int] = 5 | |||
##HTTPリクエスト成功時の待機時間 | |||
Waittime_success: Final[int] = 1 | |||
##nitterのURLのドメインとユーザーネーム部分の接続部品 | ##nitterのURLのドメインとユーザーネーム部分の接続部品 | ||
Searchquery = "search?q=from%3A" | Searchquery: Final[str] = "search?q=from%3A" | ||
##HTTPリクエスト時のユーザーエージェントとヘッダ | ##HTTPリクエスト時のユーザーエージェントとヘッダ | ||
user_agent = 'Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0' | user_agent: Final[str] = 'Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0' | ||
header = { | header = { | ||
'User-Agent': user_agent | 'User-Agent': user_agent | ||
206行目: | 86行目: | ||
##nitterでユーザーがいなかったとき返ってくるページのタイトル | ##nitterでユーザーがいなかったとき返ってくるページのタイトル | ||
##万が一仕様変更で変わったとき用 | ##万が一仕様変更で変わったとき用 | ||
Nitter_error_title = "Error|nitter" | Nitter_error_title: Final[str] = "Error|nitter" | ||
##archive.todayで魚拓がなかったときのレスポンス | |||
##万が一仕様変更で変わったとき用 | |||
Noarchive: Final[str] = "No results" | |||
##nitterで次ページ遷移時のURLから抜け落ちてる部分 | |||
Search: Final[str] = "search" | |||
##nitterの前ページ読み込み部分の名前 | |||
##万が一仕様変更で変わったとき用 | |||
Newest: Final[str] = "Load newest" | |||
#関数類 | |||
##坂根輝美に場所を知らせます | ##坂根輝美に場所を知らせます | ||
def pickupcounter(): | def pickupcounter(): | ||
220行目: | 106行目: | ||
##引数のURLにHTTP接続します | ##引数のURLにHTTP接続します | ||
##失敗かどうかは呼出側で要判定 | ##失敗かどうかは呼出側で要判定 | ||
def request_onetime(url: Final[str]) -> Response: | def request_onetime(url: Final[str]) -> requests.models.Response: | ||
return requests.get(url, timeout=Request_timeout, headers=header) | return requests.get(url, timeout=Request_timeout, headers=header,allow_redirects=False) | ||
##HTTP接続を再試行回数まで試します | ##HTTP接続を再試行回数まで試します | ||
##成功すると結果を返します | ##成功すると結果を返します | ||
## | ##接続失敗が何度も起きるとNoneを返します | ||
def request(url: Final[str]) -> Response: | ##呼出側で要None判定 | ||
def request(url: Final[str]) -> requests.models.Response: | |||
counter = 1 | counter = 1 | ||
while True: | while True: | ||
234行目: | 121行目: | ||
except requests.exceptions.RequestException as e: | except requests.exceptions.RequestException as e: | ||
if counter <= Limit_request: | if counter <= Limit_request: | ||
print(url + 'への通信失敗ナリ' + str(counter) + '/' + str(Limit_request) + '回') | print(url + 'への通信失敗ナリ ' + str(counter) + '/' + str(Limit_request) + '回') | ||
counter += 1 | counter += 1 | ||
sleep( | sleep(Waittime_error) | ||
else: | else: | ||
return None | |||
else: | else: | ||
return res | return res | ||
##URLの最後にスラッシュ付いていなければ付ける | ##URLの最後にスラッシュ付いていなければ付ける | ||
##Twitterだけスラッシュ付いていないほうが都合いいので抜く | |||
def slash_check(): | def slash_check(): | ||
global Nitterinstance | |||
global Archivetoday | |||
global Archivetodaystandard | |||
global Twitterurl | |||
if Nitterinstance[-1] != '/': | if Nitterinstance[-1] != '/': | ||
Nitterinstance = Nitterinstance + '/' | Nitterinstance = Nitterinstance + '/' | ||
252行目: | 142行目: | ||
if Archivetodaystandard[-1] != '/': | if Archivetodaystandard[-1] != '/': | ||
Archivetodaystandard = Archivetodaystandard + '/' | Archivetodaystandard = Archivetodaystandard + '/' | ||
if Twitterurl[-1] == '/': | |||
Twitterurl = Twitterurl[0:len(Twitterurl)-1] | |||
##nitterのインスタンスが生きているかチェック | ##nitterのインスタンスが生きているかチェック | ||
258行目: | 150行目: | ||
##激重インスタンスが指定されたとき試行回数増やして偶然成功してそのまま実行されるのを躱すため | ##激重インスタンスが指定されたとき試行回数増やして偶然成功してそのまま実行されるのを躱すため | ||
def instance_check(): | def instance_check(): | ||
print("nitterのインスタンスチェック中ですを") | |||
try: | try: | ||
res = request_onetime(Nitterinstance) ##リクエスト | res = request_onetime(Nitterinstance) ##リクエスト | ||
res.raise_for_status() ##HTTPステータスコードが200番台以外でエラー発生 | res.raise_for_status() ##HTTPステータスコードが200番台以外でエラー発生 | ||
sleep(Waittime_success) | |||
except requests.exceptions.RequestException as e: ##エラー発生時は終了 | except requests.exceptions.RequestException as e: ##エラー発生時は終了 | ||
print('インスタンスが死んでますを') | print('インスタンスが死んでますを') | ||
276行目: | 170行目: | ||
else: | else: | ||
res = request(Nitterinstance + account_str) ##リクエストして結果取得 | res = request(Nitterinstance + account_str) ##リクエストして結果取得 | ||
soup = bs4(res.text, 'html.parser') ## | if res is None : ##リクエスト失敗判定 | ||
fail() | |||
sleep(Waittime_success) | |||
soup = bs4(res.text, 'html.parser') ##beautifulsoupでレスポンス解析 | |||
print(account_str + "は実在の人物ではありませんでした") | if soup.title == Nitter_error_title: ##タイトルがエラーでないか判定 | ||
print(account_str + "は実在の人物ではありませんでした") ##エラー時ループに戻る | |||
else: | else: | ||
print("最終的に出会ったのが@" + account_str + "だった。") | print("最終的に出会ったのが@" + account_str + "だった。") | ||
return account_str | return account_str ##成功時アカウント名返す | ||
##検索クエリを取得 | ##検索クエリを取得 | ||
def get_query(name: Final[str]) -> Response: | def get_query(name: Final[str]) -> requests.models.Response: | ||
while True: | while True: | ||
print("検索クエリを入れるナリ。複数ある時は一つの塊ごとに入力するナリ。空欄を入れると検索開始ナリ。") | print("検索クエリを入れるナリ。複数ある時は一つの塊ごとに入力するナリ。空欄を入れると検索開始ナリ。") | ||
297行目: | 192行目: | ||
query_input = input() | query_input = input() | ||
res = request(Nitterinstance + Searchquery + '+'.join(query_str)) ##リクエストして結果取得 | res = request(Nitterinstance + Searchquery + '+'.join(query_str)) ##リクエストして結果取得 | ||
soup = bs4(res.text, 'html.parser') ## | if res is None : ##リクエスト失敗判定 | ||
fail() | |||
sleep(Waittime_success) | |||
soup = bs4(res.text, 'html.parser') ##beautifulsoupでレスポンス解析 | |||
##1件もないときはHTMLにtimeline-noneがあるから判定 | ##1件もないときはHTMLにtimeline-noneがあるから判定 | ||
if soup.find(class_='timeline-none') is None: | if soup.find(class_='timeline-none') is None: | ||
print("クエリのピースが埋まっていく。") | |||
return res ##成功したリクエストのレスポンスを返す | |||
else: | |||
print("適切なクエリを入力することを切に望む。") | print("適切なクエリを入力することを切に望む。") | ||
##接続失敗時処理 | |||
def fail(): | |||
print("接続失敗しすぎで強制終了ナリ") | |||
if txt_data != '': ##取得成功したデータがあれば発行 | |||
print("取得成功した分だけ発行しますを") | |||
make_txt() | |||
exit() ##終了 | |||
##テキスト発行 | ##テキスト発行 | ||
def make_txt( | def make_txt(): | ||
txt_data = '{|class="wikitable" style="text-align: left;"\n' + | global txt_data | ||
txt_data = '{|class="wikitable" style="text-align: left;"\n' + txt_data + '|}' ##wikiの表の最初と最後 | |||
##ファイル出力 | ##ファイル出力 | ||
with codecs.open('tweet.txt', 'w', 'utf-8') as f: | with codecs.open('tweet.txt', 'w', 'utf-8') as f: | ||
316行目: | 222行目: | ||
##記録を中断するツイート | ##記録を中断するツイート | ||
def | def stop_word() -> str: | ||
print("ここにツイートの本文を入れる!すると・・・・なんと一致するツイートで記録を中断する!(これは本当の仕様です。)ちなみに、空欄だと" + str(Limit_tweet) + "件まで終了しない。") | print("ここにツイートの本文を入れる!すると・・・・なんと一致するツイートで記録を中断する!(これは本当の仕様です。)ちなみに、空欄だと" + str(Limit_tweet) + "件まで終了しない。") | ||
end_str = input() ##ユーザー入力受付 | end_str = input() ##ユーザー入力受付 | ||
return end_str | return end_str | ||
#ページからツイート本文をtxt_dataに収めていく | |||
def get_tweet(page: Final[requests.models.Response],stop: Final[str]): | |||
global txt_data | |||
global limitcount | |||
soup = bs4(page.text, 'html.parser') ##beautifulsoupでレスポンス解析 | |||
tweets = soup.find_all(class_='timeline-item') ##一ツイートのブロックごとにリストで取得 | |||
for tweet in tweets: ##一ツイート毎に処理 | |||
doublesoup = bs4(str(tweet) , 'html.parser') ##ツイートをさらに解析 | |||
if doublesoup.a.text == Newest: ##Load Newestのボタンは処理しない | |||
continue | |||
if not doublesoup.find(class_='retweet-header') is None: ##retweet-headerはリツイートを示すので入っていれば処理しない | |||
continue | |||
tweet_url = Twitterurl + re.sub('#[^#]*$','',doublesoup.find(class_='tweet-link').get('href')) ##ツイートのURL作成 | |||
archived_tweet_url = archiveurl(tweet_url,tweet_url) ##ツイートURLをテンプレートArchiveに変化 | |||
tweet_content = doublesoup.find(class_='tweet-content media-body') ##ツイートの中身だけ取り出す | |||
triplesoup = bs4(str(tweet_content) , 'html.parser') ##URLの魚拓化のためにさらに解析 | |||
archivesoup(triplesoup) ##ツイートの中身のリンクをテンプレートArchiveに変化 | |||
txt_data = '!' + archived_tweet_url + '\n|-\n|\n' + triplesoup.get_text().replace('\n','<br>\n').replace('#','<nowiki>#</nowiki>') + '\n|-\n' + txt_data ##wikiの文法に変化 | |||
limitcount += 1 ##記録回数をカウント | |||
print("ツイートを" + str(limitcount) + "件も記録したンゴwwwwwwwwwww") | |||
if stop != '' and stop in triplesoup.get_text(): ##目的ツイートか判定 | |||
print("目的ツイート発見でもう尾張屋根") | |||
make_txt() | |||
if limitcount >= Limit_tweet: ##上限達成か判定 | |||
print(str(Limit_tweet) + "件も記録している。もうやめにしませんか。") | |||
make_txt() | |||
#soupをテンプレートArchiveに変化させる | |||
def archivesoup(soup): | |||
urls_in_tweet = soup.find_all('a') | |||
for url in urls_in_tweet: | |||
if not re.match('^https?://',url.get('href')) is None: | |||
newstr = soup.new_string(archiveurl(url.get('href'),url.text)) ##テンプレートArchiveの文字列作成 | |||
url.replace_with(newstr) ##テンプレートArchiveに変化 | |||
#URLをテンプレートArchiveに変化させる | |||
def archiveurl(url: Final[str],text: Final[str]) -> str: | |||
return '{{Archive|1=' + url + '|2=' + archive(url) + '|3=' + text + '}}' ##テンプレートArchiveの文字列返す | |||
##URLから魚拓返す | |||
def archive(url: Final[str]) -> str: | |||
archive_url = Archivetodaystandard + url | |||
res = request(Archivetoday + url) | |||
if res is None : ##魚拓接続失敗時処理 | |||
print(archive_url + 'にアクセス失敗ナリ。出力されるテキストにはそのまま記載されるナリ。') | |||
else: | |||
sleep(Waittime_success) | |||
soup = bs4(res.text, 'html.parser') ##beautifulsoupでレスポンス解析 | |||
content = soup.find(id="CONTENT").text ##archive.todayの魚拓一覧ページの中身だけ取得 | |||
if content[:len(Noarchive)] == Noarchive: | |||
print(url + "の魚拓がない。これはいけない。") | |||
else: | |||
doublesoup = bs4(res.text, 'html.parser') ##beautifulsoupでレスポンス解析 | |||
archive_url = soup.find('a').get('href').replace(Archivetoday,Archivetodaystandard) | |||
return archive_url | |||
##新しいページを取得 | |||
def getnewpage(page: Final[requests.models.Response]) -> requests.models.Response: | |||
soup = bs4(page.text, 'html.parser') ##beautifulsoupでレスポンス解析 | |||
showmores = soup.find_all(class_="show-more") | |||
for showmore in showmores: ##show-moreに次ページへのリンクか前ページへのリンクがある | |||
if showmore.text != Newest: ##前ページへのリンクではないか判定 | |||
newurl = Nitterinstance + Search + showmore.a.get('href') ##直下のaタグのhrefの中身取ってURL頭部分と合体 | |||
res = request(newurl) | |||
if res is None: | |||
fail() | |||
sleep(Waittime_success) | |||
newsoup = bs4(res.text, 'html.parser') | |||
if newsoup.find(class_="timeline-end") is None: | |||
print(res.url + 'に移動しますを') | |||
return res | |||
else: | |||
print("急に残りツイートが無くなったな終了するか") | |||
make_txt() | |||
##メイン | |||
txt_data = '' ##出力するデータ | |||
limitcount = 0 ##記録数 | |||
krsw=False | |||
if len(sys.argv) > 1 and sys.argv[1] == 'krsw': | |||
krsw=True | |||
slash_check() ##スラッシュが抜けてないかチェック | |||
instance_check() ##インスタンスが死んでないかチェック | |||
##ユーザー名取得 | |||
if krsw: | |||
print('名前は自動的に' + Callinshow + 'にナリます') | |||
name = Callinshow | |||
else: | |||
name = get_name() | |||
##検索クエリとページ取得 | |||
if krsw: | |||
print('クエリは自動的になしにナリます') | |||
page = request(Nitterinstance + Searchquery + name) | |||
if page is None: | |||
fail() | |||
else: | |||
page = get_query(name) | |||
##終わりにするツイート取得 | |||
if krsw: | |||
print('終わりにするツイートは自動的になしにナリます') | |||
stop = '' | |||
stop = | else: | ||
stop = stop_word() | |||
##ツイートを取得し終えるまでループ | |||
while True: | while True: | ||
get_tweet(page,stop) ##txt_dataにページ内のリツイート以外の全ツイートの中身突っ込んでいく | |||
page = getnewpage(page) ##新しいページ取得 | |||
--> | </syntaxhighlight> | ||
== 実行例 == | |||
20件での実行例。 | |||
{|class="wikitable" style="text-align: left;" | |||
!{{Archive|1=https://twitter.com/CallinShow/status/1536743227730333696|2=http://archivecaslytosk.onion/|3=https://twitter.com/CallinShow/status/1536743227730333696}} | |||
|- | |||
| | |||
弁護士唐澤貴洋への匿名のメッセージを募集中!<br> | |||
<nowiki>#</nowiki>マシュマロを投げ合おう<br> | |||
{{Archive|1=https://marshmallow-qa.com/apt/13e40311-bdb3-47b6-a0bd-f989f20cd603|2=https://archive.ph/https://marshmallow-qa.com/apt/13e40311-bdb3-47b6-a0bd-f989f20cd603|3=marshmallow-qa.com/apt/13e40…}} | |||
|- | |||
!{{Archive|1=https://twitter.com/CallinShow/status/1537362287949819904|2=http://archivecaslytosk.onion/|3=https://twitter.com/CallinShow/status/1537362287949819904}} | |||
|- | |||
| | |||
弁護士唐澤貴洋への匿名のメッセージを募集中!<br> | |||
<nowiki>#</nowiki>マシュマロを投げ合おう<br> | |||
{{Archive|1=https://marshmallow-qa.com/apt/a03fc481-95a5-4714-861f-3bf4d060b3ce|2=https://archive.ph/https://marshmallow-qa.com/apt/a03fc481-95a5-4714-861f-3bf4d060b3ce|3=marshmallow-qa.com/apt/a03fc…}} | |||
|- | |||
!{{Archive|1=https://twitter.com/CallinShow/status/1537752697361027072|2=http://archivecaslytosk.onion/|3=https://twitter.com/CallinShow/status/1537752697361027072}} | |||
|- | |||
| | |||
菊地さん、会社の財務諸表を年末だけではなく、来週でも開示してください。<br> | |||
<br> | |||
事前相談実施のプレスリリースって初めて見たな。 | |||
|- | |||
!{{Archive|1=https://twitter.com/CallinShow/status/1537754176939528192|2=http://archivecaslytosk.onion/|3=https://twitter.com/CallinShow/status/1537754176939528192}} | |||
|- | |||
| | |||
オレはオレで、オレのところに来た声を届けるからな。<br> | |||
<br> | |||
第二回目の相談のプレスリリースあるのか。 | |||
|- | |||
!{{Archive|1=https://twitter.com/CallinShow/status/1537795453110333440|2=http://archivecaslytosk.onion/|3=https://twitter.com/CallinShow/status/1537795453110333440}} | |||
|- | |||
| | |||
テレビに真実はあるのか。<br> | |||
<br> | |||
言いたいことが言えないテレ朝もどうなんだろうな。 | |||
|- | |||
!{{Archive|1=https://twitter.com/CallinShow/status/1537795802302918656|2=http://archivecaslytosk.onion/|3=https://twitter.com/CallinShow/status/1537795802302918656}} | |||
|- | |||
| | |||
テレビ局なんか既得権益だろ。<br> | |||
<br> | |||
在野の声をSNSを使って行っていくしかない。 | |||
|- | |||
!{{Archive|1=https://twitter.com/CallinShow/status/1537796046780497920|2=http://archivecaslytosk.onion/|3=https://twitter.com/CallinShow/status/1537796046780497920}} | |||
|- | |||
| | |||
オレたちはもう騙されないんだ。<br> | |||
<br> | |||
全てを変えるんだ。 | |||
|- | |||
!{{Archive|1=https://twitter.com/CallinShow/status/1537796546263416832|2=http://archivecaslytosk.onion/|3=https://twitter.com/CallinShow/status/1537796546263416832}} | |||
|- | |||
| | |||
なあ、この国は良くなってるのか。<br> | |||
<br> | |||
若い人は安く使われ、<br> | |||
物価は上がり、<br> | |||
公共事業だけはお金を垂れ流していく。<br> | |||
少子化が進み、年金も当てにならない。<br> | |||
<br> | |||
何でこんなに若い人が苦しまないといけないんだ。 | |||
|- | |||
!{{Archive|1=https://twitter.com/CallinShow/status/1537796983578333184|2=http://archivecaslytosk.onion/|3=https://twitter.com/CallinShow/status/1537796983578333184}} | |||
|- | |||
| | |||
若い人が全員選挙行ったらこの国は変わるぜ。<br> | |||
<br> | |||
ガチャのチケットもってるのに使わない手はないだろ。 | |||
|- | |||
!{{Archive|1=https://twitter.com/CallinShow/status/1537797605308395520|2=http://archivecaslytosk.onion/|3=https://twitter.com/CallinShow/status/1537797605308395520}} | |||
|- | |||
| | |||
👍 | |||
|- | |||
!{{Archive|1=https://twitter.com/CallinShow/status/1537798386619133952|2=http://archivecaslytosk.onion/|3=https://twitter.com/CallinShow/status/1537798386619133952}} | |||
|- | |||
| | |||
頼むよ!君にかかってる。 | |||
|- | |||
!{{Archive|1=https://twitter.com/CallinShow/status/1537799090326863872|2=http://archivecaslytosk.onion/|3=https://twitter.com/CallinShow/status/1537799090326863872}} | |||
|- | |||
| | |||
引きこもりの人は引きこもりになりたくてなったんじゃない。<br> | |||
苦しんでるんだ。<br> | |||
誰かがその声を聞く必要がある。<br> | |||
強制的に連れ出すとかやばいだろ。<br> | |||
法規制する必要あるでしょ。<br> | |||
強要罪だぜ。<br> | |||
全ては見過ごされてるんだよ。 | |||
|- | |||
!{{Archive|1=https://twitter.com/CallinShow/status/1537803303752388608|2=http://archivecaslytosk.onion/|3=https://twitter.com/CallinShow/status/1537803303752388608}} | |||
|- | |||
| | |||
この国は<br> | |||
<br> | |||
やばいだろ。<br> | |||
<br> | |||
引用リスペクト 梅野源治選手 | |||
|- | |||
!{{Archive|1=https://twitter.com/CallinShow/status/1538070416488792064|2=http://archivecaslytosk.onion/|3=https://twitter.com/CallinShow/status/1538070416488792064}} | |||
|- | |||
| | |||
本当、そうだと思う。<br> | |||
<br> | |||
貴重な意見ありがとう。 | |||
|- | |||
!{{Archive|1=https://twitter.com/CallinShow/status/1538070983827165186|2=http://archivecaslytosk.onion/|3=https://twitter.com/CallinShow/status/1538070983827165186}} | |||
|- | |||
| | |||
この国は円安が進み、外国旅行に行ったことのない若者も多く。<br> | |||
どうなるんだ。<br> | |||
<br> | |||
ライジングサンってどうなってるんだ。<br> | |||
<br> | |||
今は戦後の混乱期と同じだ。<br> | |||
<br> | |||
若い人たちで国を作るんだ。 | |||
|- | |||
!{{Archive|1=https://twitter.com/CallinShow/status/1538071752802467840|2=http://archivecaslytosk.onion/|3=https://twitter.com/CallinShow/status/1538071752802467840}} | |||
|- | |||
| | |||
立花先生は、いつも一緒懸命だ。<br> | |||
<br> | |||
信念は変わらない。<br> | |||
<br> | |||
理不尽との闘い。<br> | |||
<br> | |||
立花先生が裁判を起こすことは至極まともなことだ。<br> | |||
<br> | |||
だって最後は司法なはずだろ。<br> | |||
<br> | |||
でも、裁判は、ひどい判決も多い。<br> | |||
<br> | |||
権力に擦り寄ることもある。<br> | |||
<br> | |||
そんな全てを曝け出しているのが立花先生だ。<br> | |||
<br> | |||
こんな人今までいたか。 | |||
|- | |||
!{{Archive|1=https://twitter.com/CallinShow/status/1538072997734793216|2=http://archivecaslytosk.onion/|3=https://twitter.com/CallinShow/status/1538072997734793216}} | |||
|- | |||
| | |||
立花先生はいつも現場主義。<br> | |||
<br> | |||
人々の声を拾い。<br> | |||
<br> | |||
それを手練手管で伝えいく。<br> | |||
<br> | |||
時に道化を案じるのは、みんなに問題を気づいてほしいからだ。<br> | |||
<br> | |||
この3年間、少数政党でこれだけ目立ち続けた政党があるか。<br> | |||
<br> | |||
それは全て、NHKを変えたい、人々に良くなってもらいたい。<br> | |||
<br> | |||
その一心でしかないと思う。 | |||
|- | |||
!{{Archive|1=https://twitter.com/CallinShow/status/1538073614540754944|2=http://archivecaslytosk.onion/|3=https://twitter.com/CallinShow/status/1538073614540754944}} | |||
|- | |||
| | |||
全てをめくっていくんだよ。<br> | |||
<br> | |||
ガーシーさんは、その第一章だ。<br> | |||
<br> | |||
許せないことを言うことは大切だと。<br> | |||
<br> | |||
ガーシー党は、言わば情報公開政党ってことだろ。<br> | |||
<br> | |||
いいじゃないか、澱んだ永田町が少しは澄んでくるんじゃないか。 | |||
|- | |||
!{{Archive|1=https://twitter.com/CallinShow/status/1538074524721491968|2=http://archivecaslytosk.onion/|3=https://twitter.com/CallinShow/status/1538074524721491968}} | |||
|- | |||
| | |||
{{Archive|1=https://piped.kavin.rocks/nrJqzmMWUCU|2=https://archive.ph/https://piped.kavin.rocks/nrJqzmMWUCU|3=piped.kavin.rocks/nrJqzmMWUCU}}<br> | |||
<br> | |||
澱んだ街ごと食い荒らすんだ。 | |||
|- | |||
!{{Archive|1=https://twitter.com/CallinShow/status/1538582552423780352|2=https://archive.ph/https://twitter.com/CallinShow/status/1538582552423780352|3=https://twitter.com/CallinShow/status/1538582552423780352}} | |||
|- | |||
| | |||
弁護士唐澤貴洋への匿名のメッセージを募集中!<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…}} | |||
|- | |||
|} |
2022年6月20日 (月) 05:08時点における版
とりあえず取り急ぎ。バグ報告は利用者・トーク:夜泣き
コード
#!/usr/bin/env python3
'''
当コードは恒心停止してしまったhttps://rentry.co/7298gの降臨ショーツイート自動収集スクリプトの復刻改善版です
前開発者との出会いに感謝
〜〜〜〜〜〜〜〜〜〜〜〜〜【使い方】〜〜〜〜〜〜〜〜〜〜〜〜〜
・terminalに $ python3 (ファイル名) で作動します
・定数類は状況に応じて変えてください
・$ python3 (ファイル名) krsw コマンドライン引数をkrswとつけると自動モードです
・自動モードではユーザーは降臨ショー、クエリはなし、取得上限まで自動です
・つまりユーザー入力が要りません
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
ーーーーーーーーーーーーー【注意点】ーーーーーーーーーーーーー
・環境は玉葱前提です。
・Whonix-Workstationで動作確認済
・bs4はインストールしないと標準で入ってません
・requestsも環境によっては入っていないかもしれない
・$ pip install bs4 requests
・pipも入っていなければ$ sudo apt install pip
・バグ報告はhttps://krsw-wiki.org/wiki/?curid=15799
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
'''
#インポート類
import sys
import codecs
import re
from time import sleep
import requests
from bs4 import BeautifulSoup as bs4
from typing import Final
from urllib.parse import quote
#定数類
##nitterのインスタンス
##生きているのはhttps://github.com/zedeus/nitter/wiki/Instancesで確認
Nitterinstance = 'http://nitterqdyumlovt7tjqpdjrluitgmtpa53qq3idlpgoe4kxo7gs3xvad.onion/'
##archive.todayの魚拓
##実際にアクセスして魚拓があるか調べるのにはonionを使用
Archivetoday = 'http://archiveiya74codqgiixo33q62qlrqtkgmcitqx5u2oeqnmn5bpcbiyd.onion/'
##archive.todayの魚拓
##記事の文章に使うのはクリアネット
Archivetodaystandard = 'https://archive.ph/'
##twitterのURL
Twitterurl = 'https://twitter.com/'
##降臨ショーのユーザーネーム
Callinshow: Final[str] = 'CallinShow'
##HTTPリクエストのタイムアウト秒数
Request_timeout: Final[int] = 30
##取得するツイート数の上限
Limit_tweet: Final[int] = 100
##HTTPリクエスト失敗時の再試行回数
Limit_request: Final[int] = 5
##HTTPリクエスト失敗時の待機時間
Waittime_error: Final[int] = 5
##HTTPリクエスト成功時の待機時間
Waittime_success: Final[int] = 1
##nitterのURLのドメインとユーザーネーム部分の接続部品
Searchquery: Final[str] = "search?q=from%3A"
##HTTPリクエスト時のユーザーエージェントとヘッダ
user_agent: Final[str] = 'Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0'
header = {
'User-Agent': user_agent
}
##nitterでユーザーがいなかったとき返ってくるページのタイトル
##万が一仕様変更で変わったとき用
Nitter_error_title: Final[str] = "Error|nitter"
##archive.todayで魚拓がなかったときのレスポンス
##万が一仕様変更で変わったとき用
Noarchive: Final[str] = "No results"
##nitterで次ページ遷移時のURLから抜け落ちてる部分
Search: Final[str] = "search"
##nitterの前ページ読み込み部分の名前
##万が一仕様変更で変わったとき用
Newest: Final[str] = "Load newest"
#関数類
##坂根輝美に場所を知らせます
def pickupcounter():
print('ピックアップカウンター付近でふ')
##引数のURLにHTTP接続します
##失敗かどうかは呼出側で要判定
def request_onetime(url: Final[str]) -> requests.models.Response:
return requests.get(url, timeout=Request_timeout, headers=header,allow_redirects=False)
##HTTP接続を再試行回数まで試します
##成功すると結果を返します
##接続失敗が何度も起きるとNoneを返します
##呼出側で要None判定
def request(url: Final[str]) -> requests.models.Response:
counter = 1
while True:
try:
res = request_onetime(url)
res.raise_for_status()
except requests.exceptions.RequestException as e:
if counter <= Limit_request:
print(url + 'への通信失敗ナリ ' + str(counter) + '/' + str(Limit_request) + '回')
counter += 1
sleep(Waittime_error)
else:
return None
else:
return res
##URLの最後にスラッシュ付いていなければ付ける
##Twitterだけスラッシュ付いていないほうが都合いいので抜く
def slash_check():
global Nitterinstance
global Archivetoday
global Archivetodaystandard
global Twitterurl
if Nitterinstance[-1] != '/':
Nitterinstance = Nitterinstance + '/'
if Archivetoday[-1] != '/':
Archivetoday = Archivetoday + '/'
if Archivetodaystandard[-1] != '/':
Archivetodaystandard = Archivetodaystandard + '/'
if Twitterurl[-1] == '/':
Twitterurl = Twitterurl[0:len(Twitterurl)-1]
##nitterのインスタンスが生きているかチェック
##死んでいたらそこで終了
##接続を一回しか試さないrequest_onetimeを使っているのは
##激重インスタンスが指定されたとき試行回数増やして偶然成功してそのまま実行されるのを躱すため
def instance_check():
print("nitterのインスタンスチェック中ですを")
try:
res = request_onetime(Nitterinstance) ##リクエスト
res.raise_for_status() ##HTTPステータスコードが200番台以外でエラー発生
sleep(Waittime_success)
except requests.exceptions.RequestException as e: ##エラー発生時は終了
print('インスタンスが死んでますを')
exit()
##ツイート収集するユーザー名を取得
##何も入力しないと尊師を指定するよう改良
def get_name() -> str:
while True:
print('アカウント名を入れなければない。空白だと自動的に' + Callinshow + 'になりますを')
account_str = input() ##ユーザー入力受付
##空欄で降臨ショー
if account_str == '':
return Callinshow
else:
res = request(Nitterinstance + account_str) ##リクエストして結果取得
if res is None : ##リクエスト失敗判定
fail()
sleep(Waittime_success)
soup = bs4(res.text, 'html.parser') ##beautifulsoupでレスポンス解析
if soup.title == Nitter_error_title: ##タイトルがエラーでないか判定
print(account_str + "は実在の人物ではありませんでした") ##エラー時ループに戻る
else:
print("最終的に出会ったのが@" + account_str + "だった。")
return account_str ##成功時アカウント名返す
##検索クエリを取得
def get_query(name: Final[str]) -> requests.models.Response:
while True:
print("検索クエリを入れるナリ。複数ある時は一つの塊ごとに入力するナリ。空欄を入れると検索開始ナリ。")
print("例:「無能」→改行→「脱糞」→改行→「弟殺し」→改行→空欄で改行")
query_str = [name] ##検索クエリ文字列をquery_strに収納。ユーザー名を先に含めておく
query_input = input() ##ユーザー入力受付
##空欄が押されるまでユーザー入力受付
while query_input != '':
query_str.append(quote(query_input))
query_input = input()
res = request(Nitterinstance + Searchquery + '+'.join(query_str)) ##リクエストして結果取得
if res is None : ##リクエスト失敗判定
fail()
sleep(Waittime_success)
soup = bs4(res.text, 'html.parser') ##beautifulsoupでレスポンス解析
##1件もないときはHTMLにtimeline-noneがあるから判定
if soup.find(class_='timeline-none') is None:
print("クエリのピースが埋まっていく。")
return res ##成功したリクエストのレスポンスを返す
else:
print("適切なクエリを入力することを切に望む。")
##接続失敗時処理
def fail():
print("接続失敗しすぎで強制終了ナリ")
if txt_data != '': ##取得成功したデータがあれば発行
print("取得成功した分だけ発行しますを")
make_txt()
exit() ##終了
##テキスト発行
def make_txt():
global txt_data
txt_data = '{|class="wikitable" style="text-align: left;"\n' + txt_data + '|}' ##wikiの表の最初と最後
##ファイル出力
with codecs.open('tweet.txt', 'w', 'utf-8') as f:
f.write(txt_data)
print("テキストファイル手に入ったやで〜")
exit() ##終了
##記録を中断するツイート
def stop_word() -> str:
print("ここにツイートの本文を入れる!すると・・・・なんと一致するツイートで記録を中断する!(これは本当の仕様です。)ちなみに、空欄だと" + str(Limit_tweet) + "件まで終了しない。")
end_str = input() ##ユーザー入力受付
return end_str
#ページからツイート本文をtxt_dataに収めていく
def get_tweet(page: Final[requests.models.Response],stop: Final[str]):
global txt_data
global limitcount
soup = bs4(page.text, 'html.parser') ##beautifulsoupでレスポンス解析
tweets = soup.find_all(class_='timeline-item') ##一ツイートのブロックごとにリストで取得
for tweet in tweets: ##一ツイート毎に処理
doublesoup = bs4(str(tweet) , 'html.parser') ##ツイートをさらに解析
if doublesoup.a.text == Newest: ##Load Newestのボタンは処理しない
continue
if not doublesoup.find(class_='retweet-header') is None: ##retweet-headerはリツイートを示すので入っていれば処理しない
continue
tweet_url = Twitterurl + re.sub('#[^#]*$','',doublesoup.find(class_='tweet-link').get('href')) ##ツイートのURL作成
archived_tweet_url = archiveurl(tweet_url,tweet_url) ##ツイートURLをテンプレートArchiveに変化
tweet_content = doublesoup.find(class_='tweet-content media-body') ##ツイートの中身だけ取り出す
triplesoup = bs4(str(tweet_content) , 'html.parser') ##URLの魚拓化のためにさらに解析
archivesoup(triplesoup) ##ツイートの中身のリンクをテンプレートArchiveに変化
txt_data = '!' + archived_tweet_url + '\n|-\n|\n' + triplesoup.get_text().replace('\n','<br>\n').replace('#','<nowiki>#</nowiki>') + '\n|-\n' + txt_data ##wikiの文法に変化
limitcount += 1 ##記録回数をカウント
print("ツイートを" + str(limitcount) + "件も記録したンゴwwwwwwwwwww")
if stop != '' and stop in triplesoup.get_text(): ##目的ツイートか判定
print("目的ツイート発見でもう尾張屋根")
make_txt()
if limitcount >= Limit_tweet: ##上限達成か判定
print(str(Limit_tweet) + "件も記録している。もうやめにしませんか。")
make_txt()
#soupをテンプレートArchiveに変化させる
def archivesoup(soup):
urls_in_tweet = soup.find_all('a')
for url in urls_in_tweet:
if not re.match('^https?://',url.get('href')) is None:
newstr = soup.new_string(archiveurl(url.get('href'),url.text)) ##テンプレートArchiveの文字列作成
url.replace_with(newstr) ##テンプレートArchiveに変化
#URLをテンプレートArchiveに変化させる
def archiveurl(url: Final[str],text: Final[str]) -> str:
return '{{Archive|1=' + url + '|2=' + archive(url) + '|3=' + text + '}}' ##テンプレートArchiveの文字列返す
##URLから魚拓返す
def archive(url: Final[str]) -> str:
archive_url = Archivetodaystandard + url
res = request(Archivetoday + url)
if res is None : ##魚拓接続失敗時処理
print(archive_url + 'にアクセス失敗ナリ。出力されるテキストにはそのまま記載されるナリ。')
else:
sleep(Waittime_success)
soup = bs4(res.text, 'html.parser') ##beautifulsoupでレスポンス解析
content = soup.find(id="CONTENT").text ##archive.todayの魚拓一覧ページの中身だけ取得
if content[:len(Noarchive)] == Noarchive:
print(url + "の魚拓がない。これはいけない。")
else:
doublesoup = bs4(res.text, 'html.parser') ##beautifulsoupでレスポンス解析
archive_url = soup.find('a').get('href').replace(Archivetoday,Archivetodaystandard)
return archive_url
##新しいページを取得
def getnewpage(page: Final[requests.models.Response]) -> requests.models.Response:
soup = bs4(page.text, 'html.parser') ##beautifulsoupでレスポンス解析
showmores = soup.find_all(class_="show-more")
for showmore in showmores: ##show-moreに次ページへのリンクか前ページへのリンクがある
if showmore.text != Newest: ##前ページへのリンクではないか判定
newurl = Nitterinstance + Search + showmore.a.get('href') ##直下のaタグのhrefの中身取ってURL頭部分と合体
res = request(newurl)
if res is None:
fail()
sleep(Waittime_success)
newsoup = bs4(res.text, 'html.parser')
if newsoup.find(class_="timeline-end") is None:
print(res.url + 'に移動しますを')
return res
else:
print("急に残りツイートが無くなったな終了するか")
make_txt()
##メイン
txt_data = '' ##出力するデータ
limitcount = 0 ##記録数
krsw=False
if len(sys.argv) > 1 and sys.argv[1] == 'krsw':
krsw=True
slash_check() ##スラッシュが抜けてないかチェック
instance_check() ##インスタンスが死んでないかチェック
##ユーザー名取得
if krsw:
print('名前は自動的に' + Callinshow + 'にナリます')
name = Callinshow
else:
name = get_name()
##検索クエリとページ取得
if krsw:
print('クエリは自動的になしにナリます')
page = request(Nitterinstance + Searchquery + name)
if page is None:
fail()
else:
page = get_query(name)
##終わりにするツイート取得
if krsw:
print('終わりにするツイートは自動的になしにナリます')
stop = ''
else:
stop = stop_word()
##ツイートを取得し終えるまでループ
while True:
get_tweet(page,stop) ##txt_dataにページ内のリツイート以外の全ツイートの中身突っ込んでいく
page = getnewpage(page) ##新しいページ取得
実行例
20件での実行例。