[PYTHON] Eine Geschichte, die die Lieferung von Nico Nama analysierte.

Einführung

Ich bin ein VTuber Limit Nerd. Im Dezember fand in Ryogoku Kokugikan "Virtual to Live" statt, das auch von Nico Nama vertrieben wurde. Der Verlierer konnte nicht auf die Website gehen und sie sogar in Echtzeit sehen, aber er konnte nicht warten, bis die CD herauskam. Deshalb habe ich ein Online-Ticket gekauft und die Zeitverschiebung gesehen. Nun, lass uns sterben. Ich wollte es speichern, bevor ich die Zeitverschiebung sehen konnte, also begann ich, Nikosei mit Python in der Hand zu analysieren.

Meine Stärke liegt übrigens in "Was ist Web Socket?", "Hls?", "Selen?".

Inhalt von Nico raw

** * Hinweis: Informationen zum 17. Dezember 2019. Die Spezifikationen von Nico Nama können sich in Zukunft ändern. ** **.

Die Analyse nutzte die DevTools von Chrome. Sie können es anzeigen, indem Sie in Chrome "F12" drücken. Öffnen wir zunächst eine geeignete Distribution. Wenn Sie nach dem Öffnen der Distribution F12 drücken, laden Sie sie einmal neu.

HLS Auf der Registerkarte "Netzwerk" von DevTools können Sie die Kommunikationsprotokolle für diese Website anzeigen. (Wenn Sie "Cache deaktivieren" aktivieren, wird das Protokoll auch dann nicht ausgeblendet, wenn die Seite verschoben wird.) Für die Videoverteilung suchte ich nach der Kommunikation mit der größten "Größe". Dann https: // {???} .dmc.nico / hlsarchive / ht2_nicolive / nicolive-hamster- {Verteilungs-ID} main {16 hexadezimale Zahl} / 4 / ts / {numerischer Wert} .ts? Ich habe eine große Datenmenge von einer solchen URL heruntergeladen. Die{number}scheint in der Reihenfolge zuzunehmen. Und von einer ähnlichen URL https: // {???} .dmc.nico / hlsarchive / ht2_nicolive / nicolive-hamster- {Verteilungs-ID} main {16 hexadezimale Zahl} / 4 / ts / playlist.m3u8?` Mit der Antwort von

playlist


#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:5

(Unterlassung)

Daten wie diese wurden gesendet. Als ich mit ".m3u8" und ".ts" nachgesehen habe, scheint es sich um eine Datei zu handeln, die vom Protokoll "HLS (HTTP Live Streaming)" verwendet wird. Dieses "HLS" scheint mit "ffmpeg" einfach zu DL zu sein. Sie müssen also nur die URL abrufen.

URL-Erfassung

Woher hast du diese URL?

Drücken Sie "Strg + F" auf der Registerkarte "Netzwerk" von DevTools. Dort fand ich "https: // {???}. Dmc.nico/hlsarchive/ht2_nicolive/nicolive-hamster-{Distribution ID} main {16 hexadezimale Zahl}" Lassen Sie uns durch Eingabe suchen. Anscheinend kommt "Kommunikation mit URL" heraus und "Kommunikation mit URL" wird nicht gefunden.

Aufgrund verschiedener Versuche wie "Was meinst du?" Scheint die Kommunikation im Web-Socket nicht durchsucht werden zu können.

Suchen Sie also mit wss:. Dann finden Sie 3 Web-Sockets. image.png

4012 / scheint Binärdaten aneinander zu senden. Lass es vorerst. websocket empfängt Chat-Informationen vom Server im JSON-Format. Sie können dies auch verlassen.

Der verbleibende Web-Socket (hier "Timeshift") ist auf einen Blick nicht ersichtlich. Lassen Sie uns nach der URL suchen, nach der Sie suchen. Dann gab es eine Hit-Kommunikation. image.png Es scheint, dass diese Kommunikation JSON empfängt. Ich habe die gesuchte URL in "uri" in diesem JSON gefunden.

Are you still watching? Nun, das ist natürlich zu sagen, aber es scheint, dass die URL ungültig wird, wenn der Server beurteilt, dass "ich diesen Kerl nicht mehr gesehen habe". Daher ist es nicht möglich zu sagen "Holen Sie sich die URL und lassen Sie es in Ruhe".

Was soll ich dann tun? Ich kam auf die Idee Es war eine Methode, "es Nico Nico zu überlassen". Wenn Sie die Verteilungs-URL in Selenium offen lassen, sendet der Client automatisch "Ich schaue noch" an den Server. In der Zwischenzeit wird "ffmpeg" DL, was eine einfache Geschichte ist. Diese Methode ist jedoch in Ordnung, wenn Sie sie nur herunterladen. Ich denke jedoch, dass sie nicht verwendet werden kann, wenn es um "Ich möchte sie mit meiner eigenen Anwendung spielen!" Geht. In diesem Fall müssen Sie "Ich schaue immer noch" selbst senden Es gibt. Außerdem kann Nico Nico die Lieferung jeweils nur in einem Fenster sehen (ich bin mit Nico Nico nicht vertraut, daher kenne ich die Details nicht). Bitte beachten Sie, dass DL beendet wird, wenn Sie die Distribution während DL öffnen.

Schreiben Sie ein Skript in Python

Code

shell


python dl.py {live_id} {id} {pass}

Sie können es mit tun.

dl.py


from selenium import webdriver
import chromedriver_binary
import json
import time
import sys
import subprocess

lid = sys.argv[1]
id = sys.argv[2]
pa = sys.argv[3]


options = webdriver.ChromeOptions()
options.add_argument('--headless')
caps = webdriver.DesiredCapabilities.CHROME
caps['goog:loggingPrefs'] = {'performance': 'ALL'}
driver = webdriver.Chrome(
    options=options, desired_capabilities=caps, service_log_path='NUL')

driver.get('https://account.nicovideo.jp/login')
fid = driver.find_element_by_xpath('//*[@id="input__mailtel"]')
fpa = driver.find_element_by_xpath('//*[@id="input__password"]')
fid.clear()
fid.send_keys(id)
fpa.clear()
fpa.send_keys(pa)
fpa.submit()

driver.get('https://live2.nicovideo.jp/watch/' + lid)
# setting_button = driver.find_element_by_xpath(
#     '/html/body/div/div/div[4]/div[3]/div/div/div[1]/div[3]/div[1]/div[2]/div[3]/button[4]').click()
# time.sleep(1)
# driver.find_element_by_xpath(
#     '/html/body/div/div/div[4]/div[3]/div/div/div[1]/div[3]/div[1]/div[2]/div[4]/div/div/div[2]').click()
# time.sleep(1)
# driver.find_element_by_xpath(
#     '/html/body/div/div/div[4]/div[3]/div/div/div[1]/div[3]/div[1]/div[2]/div[4]/section[2]/ul/div[2]').click()
# time.sleep(3)
# driver.find_element_by_xpath(
#     '/html/body/div/div/div[4]/div[3]/div/div/div[1]/div[3]/div[1]/div[2]/button').click()

time.sleep(3)
log = [json.loads(i['message']) for i in driver.get_log(
    'performance') if json.loads(i['message'])['message']['method'] == 'Network.webSocketFrameReceived']
log = [json.loads(i['message']['params']['response']['payloadData'])
       for i in log if i['message']['params']['response']['payloadData'][0] == '{']
log = [i['body'] for i in log if 'body' in i.keys()]

uri = ''
quality = 6
for i in log:
    if 'command' in i.keys():
        if i['command'] == 'currentstream':
            if 0 != i['currentStream']['qualityTypes'].index(i['currentStream']['quality']) < quality:
                uri = i['currentStream']['uri']
                quality = i['currentStream']['qualityTypes'].index(
                    i['currentStream']['quality'])
            if quality == 0:
                break

subprocess.run(['ffmpeg', '-i', uri, '-c', 'copy', 'output.mp4'])
driver.quit()

Kommentar

Chromeinstellungen

options = webdriver.ChromeOptions()
options.add_argument('--headless')
caps = webdriver.DesiredCapabilities.CHROME
caps['goog:loggingPrefs'] = {'performance': 'ALL'}
driver = webdriver.Chrome(
    options=options, desired_capabilities=caps, service_log_path='NUL')

Sie können es ausführen, ohne das Fenster anzuzeigen, indem Sie die Option "--headless" hinzufügen. caps ['goog: loggingPrefs'] = {'performance': 'ALL'} ist die Einstellung zum Anzeigen des Kommunikationsprotokolls. Ich bin mir über die Details nicht sicher.

Einloggen

driver.get('https://account.nicovideo.jp/login')
fid = driver.find_element_by_xpath('//*[@id="input__mailtel"]')
fpa = driver.find_element_by_xpath('//*[@id="input__password"]')
fid.clear()
fid.send_keys(id)
fpa.clear()
fpa.send_keys(pa)
fpa.submit()

Nico-Schüler können nicht gesehen werden, ohne sich anzumelden. Melden Sie sich daher hier einmal an.

Websocket-Überwachung

driver.get('https://live2.nicovideo.jp/watch/' + lid)
# setting_button = driver.find_element_by_xpath(
#     '/html/body/div/div/div[4]/div[3]/div/div/div[1]/div[3]/div[1]/div[2]/div[3]/button[4]').click()
# time.sleep(1)
# driver.find_element_by_xpath(
#     '/html/body/div/div/div[4]/div[3]/div/div/div[1]/div[3]/div[1]/div[2]/div[4]/div/div/div[2]').click()
# time.sleep(1)
# driver.find_element_by_xpath(
#     '/html/body/div/div/div[4]/div[3]/div/div/div[1]/div[3]/div[1]/div[2]/div[4]/section[2]/ul/div[2]').click()
# time.sleep(3)
# driver.find_element_by_xpath(
#     '/html/body/div/div/div[4]/div[3]/div/div/div[1]/div[3]/div[1]/div[2]/button').click()

time.sleep(3)
log = [json.loads(i['message']) for i in driver.get_log(
    'performance') if json.loads(i['message'])['message']['method'] == 'Network.webSocketFrameReceived']
log = [json.loads(i['message']['params']['response']['payloadData'])
       for i in log if i['message']['params']['response']['payloadData'][0] == '{']
log = [i['body'] for i in log if 'body' in i.keys()]

uri = ''
quality = 6
for i in log:
    if 'command' in i.keys():
        if i['command'] == 'currentstream':
            if i['currentStream']['qualityTypes'].index(i['currentStream']['quality']) < quality:
                uri = i['currentStream']['uri']
                quality = i['currentStream']['qualityTypes'].index(
                    i['currentStream']['quality'])
            if quality == 0:
                break

Sie können das Leistungsprotokoll mit "driver.get_log (" Leistung ")" abrufen. if json.loads (i ['message']) ['message'] ['method'] == 'Network.webSocketFrameReceived' extrahiert nur den Empfang von Websocket. Von dort werden wir nur diejenigen belassen, die "JSON-Format haben und einen" Körper "haben. Die URL wird mit "uri = i ['currentStream'] ['uri']" gespeichert. Hier versuche ich, die "beste URL" aus der Kommunikation zu erhalten. Im Kommentarbereich wählen wir das Video mit der höchsten Qualität aus und stoppen das Video. Ich habe dies auskommentiert, da es sich sehr wahrscheinlich in zukünftigen Updates ändern wird. Wir empfehlen Ihnen, die Verwendung von Selen zu untersuchen und selbst zu schreiben.

Speichern und schließen

subprocess.run(['ffmpeg', '-i', uri, '-c', 'copy', 'output.mp4'])
driver.quit()

Ich führe ffmpeg mit subprocess.run aus (['ffmpeg', '- i', uri, '- c', 'copy', 'output.mp4']). ffmpeg muss bereits installiert sein. Schließen Sie Chrome mit 'driver.quit ()'.

Schließlich

Selbst wenn Sie einen Webdienst analysieren und weltweit veröffentlichen, wird er häufig aufgrund von Änderungen in den Dienstspezifikationen unbrauchbar. Dieses Mal wollte ich nicht die "aktuellen Spezifikationen" teilen, sondern "Ist es nicht möglich, die Spezifikationen auf diese Weise zu analysieren?". Also wurde dieser Artikel so geschrieben. Wie ich am Anfang sagte, teile ich es, ohne etwas zu wissen. Wenn Sie denken "ist es hier seltsam?", Weisen Sie bitte darauf hin.

Recommended Posts

Eine Geschichte, die die Lieferung von Nico Nama analysierte.
Eine Geschichte, die den Aufwand für Betrieb / Wartung reduziert
Die Geschichte des Exportierens eines Programms
Die Geschichte der Erstellung einer Website, auf der die Veröffentlichungsdaten von Büchern aufgeführt sind
Die Geschichte der Verarbeitung A von Blackjack (Python)
Die Geschichte, ein Modul zu erstellen, das E-Mails mit Python überspringt
Eine Geschichte, die die Gegenwart von Qiita mit Qiita API + Elasticsearch + Kibana visualisiert
Die Geschichte der Entwicklung einer WEB-Anwendung, die automatisch Fangkopien generiert [MeCab]
Die Geschichte, ein Paket zu erstellen, das den Betrieb von Juman (Juman ++) & KNP beschleunigt
Die Geschichte eines Mel-Icon-Generators
Die Geschichte von sys.path.append ()
Die Geschichte einer Box, die Peppers AL Memory und MQTT miteinander verbindet
Die Geschichte der Erstellung einer Webanwendung, die umfangreiche Lesungen mit Django aufzeichnet
Die Geschichte von Django, wie er eine Bibliothek erstellt, die vielleicht etwas nützlicher ist
Erstellen Sie einen BOT, der die Discord-URL verkürzt
#Eine Funktion, die den Zeichencode einer Zeichenfolge zurückgibt
Die Geschichte, eine harte Zeit mit der gemeinsamen Menge HTTP_PROXY = ~ zu haben
Erzeugen Sie diese Form des Bodens einer Haustierflasche
Eine Geschichte über die Änderung des Master-Namens von BlueZ
Die Geschichte, dass der Rückgabewert von tape.gradient () None war
Zip 4 Gbyte Problem ist eine Geschichte der Vergangenheit
[Python] Ein Programm, das die Positionen von Kängurus vergleicht.
Die Geschichte des Baus von Zabbix 4.4
Ein Werkzeug, das die Gacha von Soshage automatisch dreht
Die Geschichte der Einrichtung eines VIP-Kanals im internen Chatwork
Die Geschichte des Django-Modellfeldes verschwindet aus der Klasse
Die Geschichte des Erstellens einer Datenbank mithilfe der Google Analytics-API
Die Geschichte, wie man mit discord.py einen Fragenkasten-Bot erstellt
Python-Skript, das den Inhalt zweier Verzeichnisse vergleicht
Beim Inkrementieren des Werts eines Schlüssels, der nicht vorhanden ist
Eine Geschichte, die mit der Installation der maschinellen Lernbibliothek JAX zusammenhängt
Die Geschichte, dass die Version von Python 3.7.7 nicht an Heroku angepasst wurde
Die Geschichte von Python und die Geschichte von NaN
pandas Ruft den Namen einer Spalte ab, die ein bestimmtes Zeichen enthält
Die Geschichte, dass ein Hash-Fehler bei der Verwendung von Pipenv auftrat
Eine Formel, die einfach das Alter ab dem Geburtsdatum berechnet
Eine Geschichte, die bestätigte, ob die Zahl der Coronas bei jungen Menschen wirklich schnell zunimmt
Die Geschichte, einen Standardtreiber für db mit Python zu erstellen.
Eine Funktion, die die Verarbeitungszeit einer Methode in Python misst
Die Geschichte der Release-Arbeit der Anwendung, die Google nicht erzählt
Die Geschichte des "Lochs" in der Akte
Eine kleine süchtig machende Geschichte mit den Berechtigungen des von expdp angegebenen Verzeichnisses (für Anfänger)
Ich habe einen schlaffen Bot gemacht, der mich über die Temperatur informiert
Die Geschichte, ein Tool zu erstellen, das auf Mac und Windows auf der Spieleentwicklungsseite ausgeführt wird
Die Geschichte des erneuten Bereitstellens des Anwendungsservers
[Python] Ein Hinweis, dass ich das Verhalten von matplotlib.pyplot zu verstehen begann
Die Geschichte des Erstellens eines Bots, der aktive Mitglieder in einem bestimmten Slack-Kanal mit Python anzeigt
[Python] Ein Programm, das den Inhalt der Liste nach links dreht
Die Geschichte, einen Slackbot zu erstellen, der beim Senden des Verarbeitungscodes ein GIF oder PNG ausgibt
[AtCoder für Anfänger] Sprechen Sie über den Rechenaufwand, den Sie grob wissen möchten
Eine Geschichte über das Erstellen eines Programms, mit dem die Anzahl der Instagram-Follower in einer Woche von 0 auf 700 erhöht wird
Die Geschichte eines Parksensors in 10 Minuten mit dem GrovePi + Starter Kit
[Python] Ein Programm, das die Anzahl der Schokoladensegmente berechnet, die die Bedingungen erfüllen
Die Geschichte, wie man mit Python einen 100-Yen-Frühstücks-Bot für die Universität macht
Ich habe einen Kalender erstellt, der den Verteilungsplan von Vtuber automatisch aktualisiert
[Python] Ein Programm, das die Anzahl der gepaarten Socken berechnet