クリボッチなのでえっちなイラストをニ●エで無限に収集する

**[IPFactory Advent Calender2019](https://qiita.com/advent-calendar/2019/ipfactory)**の最終日になってしまってます。

さぁクリボッチの皆!えっちなイラストで性の6時間を乗り切ろうぜ!!
あ、もう終わってるわ

#仕様
Webスクレイピングを利用して●ジエに投稿された画像を自動で収集する。ちょろっと変えればP●x●vとかも。
#中身
GitHubにも入れときます~~(後で)~~

```python
from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException
import time
import urllib.error
import urllib.request

url_few = "http://nijie.info/view.php?id="
url_many = "http://nijie.info/view_popup.php?id="



def download_image(url, file_name):
   try:
       headers = {
       "User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:47.0) Gecko/20100101 Firefox/47.0"
       }
       data = urllib.request.urlopen(urllib.request.Request(url, headers=headers))
       with open(file_name, mode="wb") as f:
           f.write(data.read())
   except urllib.error.URLError as e:
       print(e)

with open("delete_list.txt", mode="r") as del_ls:
    del_ls=(del_ls.read()).split(",")

browser = webdriver.Chrome()
browser.get("https://nijie.info/login.php")
tmp = browser.find_element_by_id("slide_login_button")
tmp.click()

WebDriverWait(browser, 15).until(EC.presence_of_element_located((By.TAG_NAME, 'input')))
tmp = browser.find_elements_by_tag_name("input")

# ログイン処理
try:
    tmp[0].send_keys("mailaddress")
    tmp[1].send_keys("password")
    tmp[2].click()
except:
    tmp = browser.find_elements_by_tag_name("input")
    tmp[0].send_keys("mailaddress")
    tmp[1].send_keys("password")
    tmp[2].click()


soup = BeautifulSoup(browser.page_source, "html.parser")
max_id = int(soup.find(class_="mozamoza ngtag")["illust_id"])

for i in range(1 + int(del_ls[-1]), max_id + 1):

    if str(i) in del_ls:
        pass
    else:

        browser.get(url_few + str(i))
        view_soup = BeautifulSoup(browser.page_source, "html.parser")
        # 画像が生きてるか
        if len(view_soup.find_all(class_="m-top3")) == 2:

            # JSが表示されてるか
            try:
                int(view_soup.find(class_="m-top3").span.string)
            except:
                time.sleep(1)
                view_soup = BeautifulSoup(browser.page_source, "html.parser")
            # 閾値(閲覧数)いいね、タグ等他の要素はまた別の所を見る
            if int(view_soup.find(class_="m-top3").span.string) > 10000:
                soup = view_soup.find_all(class_="mozamoza ngtag")
                img_id = [(int(n["illust_id"])) for n in soup].index(i)
                name = [str(soup[img_id]["alt"]), str(soup[img_id]["src"])[-4:]]

                # 画像が複数枚投稿されているときの処理
                if int(soup[img_id + 1]["illust_id"]) == i:
                    browser.get(url_many + str(i))
                    WebDriverWait(browser, 15).until(EC.presence_of_element_located((By.CLASS_NAME, 'box-shadow999')))
                    tmp = [(str(n["src"])) for n in BeautifulSoup(browser.page_source, "html.parser").find_all(class_="box-shadow999")]

                    # 画像が単一投稿
                else:
                    tmp = [str(soup[img_id]["src"])]

                # DL処理、例外はファイル名にできない文字をすっ飛ばす
                # 文字列連結は最終的に(URL, ./images/イラストのタイトル_ページ数.拡張子)になる
                for n in range (len(tmp)):
                    try:
                        download_image("http:" + tmp[n], "./images/"  + name[0] + "_" + str(n) + name[1])
                    except:
                        name[0] = name[0].replace("/", "").replace("\\", "").replace("*", "").replace("<", "").replace(">", "").replace("|", "").replace("?", "").replace(":", "")
                        download_image("http:" + tmp[n], "./images/"  + name[0] + "_" + str(n) + name[1])

        # 既に削除されたillust_idを改行無しで追記
        else:
            with open("delete_list.txt", mode="a") as t:
                print("," + str(i), end="", flush=True, file=t)
        # 此処を弄れば爆速になるけどおそらく鯖の限界ギリギリ
        if i % 4 == 0:
            time.sleep(1.5)

```

#躓いた箇所

##403error
画像ダウンロード時urllibを使う。がその際ユーザーエージェントが無いと弾かれてしまう問題
鯖「よくわからない奴には返事なんかしないもんねー!」

```python

   headers = {
   "User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:47.0) Gecko/20100101 Firefox/47.0"
   }
   data = urllib.request.urlopen(urllib.request.Request(url, headers=headers))
   with open(file_name, mode="wb") as f:
       f.write(data.read())
   
```
こんな感じで適当につけてOK…なはず。

##要素の表示までWaitしてるのに値が取ってこれない
画面は遷移した。要素もある。が値が入ってないことはままある。
WebDriverWait「我輩はinput要素である。(値はまだない)」

```python

WebDriverWait(browser, 15).until(EC.presence_of_element_located((By.TAG_NAME, 'input')))
tmp = browser.find_elements_by_tag_name("input")
try:
    正常処理
except:
    tmp = browser.find_elements_by_tag_name("input")
    正常処理
```
わざわざtime使わなくても一瞬で表示されるのでこれで十分だったり
JSの処理が挟まってたりしたらまた違う待ち方もあるけどやっぱり一瞬なのでこれで十分

##print()が出来ない!!
無駄な探索(消された画像の表示)をしないで済むように.txtに追記をしたかった。
がwrite()を使うと何故か文字が逐次書き込まれない
(with~~でopenしているのでclose忘れでは無い(試しにmode="w"だとしっかり消された上で何も書き込まれない))

のでprintで行った。が出力されない!**ｱｲｴｴｴ!?ﾅﾝﾃﾞ!?**
どうもprint関数は改行文字がない場合いつかまとめて出力(改行)するその日までバッファに溜め込むことが発覚した。
print()「フッ…俺が最適化しておいてやるよ(賢くて有能な俺ｶｯｹｰ!)」

```python
with open("delete_list.txt", mode="a") as t:
    print("," + str(i), end="", flush=True, file=t)
```
`flush=True`で強制的に吐かせる事ができるらしい。良い事覚えた。

##429error
鯖側のAPIがかけてる許容リクエスト数を超えたことで起こる
鯖「リクエスト送りすぎ!APIが耐えられないよ!」

```python
if i % 4 == 0:
    time.sleep(1.5)
```
適当に待ちましょう


#┌|∵|┘に刺さる言葉
##ログイン処理とか直接パラメータ送れば???
ごめん、めんどくさかったんだ。
普段の操作と同じように動かせるのがseleniumの良いところなので存分に甘えた結果です。
更にセッション管理とか上手いことやればcurl,wgetで出来るっちゃできるんですけどめっちゃめんどいから許して。

##画面出てきてウザいんですが
ヘッドレスモードなるものがあって[こんな感じで](https://qiita.com/orangain/items/db4594113c04e8801aad)4行程度追加すると裏で起動します。
どんな挙動がおきてるか視覚的にわからないのは開発時厄介なのでぜひ完成してからオプションをつけることをオススメします。


PhantomJSは死んだんだ
いくら呼んでも帰っては来ないんだ
もうあの時間は終わって、君もchromiumと向き合う時なんだ

##閾値閲覧数だけじゃねえか。タグといいねも見ろよ。~~これじゃただの閲覧数検索だぞ~~

すみません。
改変するだけで出来ると思うのでよろしくお願いします。

##クローリングできないが????
すみません。
一番大きなfor文をいじったりcronで回すとかで対応できるはずです。いずれします。~~予定は未定~~

##は????遅いんだが??????
サイト側の同一IPでのリクエスト制限。各イラストページにアクセスしなければならない関係上結構限界速度だったりします。
もし最速で集めたいのであれば複数IPでリクエストを送ることですがそれ普通にDDoSって言われてるんで。


#謝罪
**[IPFactory Advent Calender2019](https://qiita.com/advent-calendar/2019/ipfactory)**の最後を飾る記事が技術力もないただの下ネタ消化になってしまって申し訳なさの塊。
軽い気持ちで参加したと思ったら最終日しか空いてなくてな…
そして前日まで完成しなかったせいで交代というのも出来なくてただただ申し訳ない。

そしてニジ●様にも(節度はあるが社会性のない)多大なリクエストを投げつける結果になってしまい心から申し訳ないと思っています。