[PYTHON] Une histoire qui a analysé la livraison de Nico Nama.

introduction

Je suis un nerd de limite VTuber. En décembre, "Virtual to Live" a eu lieu au Ryogoku Kokugikan, et il a également été distribué par Nico Nama. Le perdant ne pouvait pas aller sur le site et même le voir en temps réel, mais il ne pouvait pas attendre la sortie du disque. C'est pourquoi j'ai acheté un billet en ligne et vu le décalage horaire. Eh bien, mourons. Je voulais le sauvegarder avant de voir le décalage horaire, alors j'ai commencé à analyser Nikosei avec Python dans ma main.

Au fait, ma force est de savoir "qu'est-ce que web socket?", "Hls?", "Selenium?".

Contenu de Nico raw

** * Remarque: informations en date du 17 décembre 2019. Les spécifications de Nico Nama peuvent changer à l'avenir. ** **

L'analyse a tiré parti des DevTools de Chrome. Vous pouvez l'afficher en appuyant sur «F12» sur Chrome. Tout d'abord, ouvrons une distribution appropriée. Si vous appuyez sur F12 après avoir ouvert la distribution, rechargez-la une fois.

HLS Dans l'onglet Réseau de DevTools, vous pouvez voir les journaux de communication de ce site Web. (Si vous cochez «Désactiver le cache», le journal ne disparaîtra pas même si la page se déplace) Pour la distribution vidéo, j'ai recherché la communication avec le plus grand "Size". Puis https: // {???} .dmc.nico / hlsarchive / ht2_nicolive / nicolive-hamster- {Delivery ID} _main_ {16 nombre hexadécimal} / 4 / ts / {nombre} .ts? Je téléchargeais une grande quantité de données à partir d'une URL comme celle-ci. Le "{nombre}" semble augmenter dans l'ordre. Et d'une URL similaire https: // {???} .dmc.nico / hlsarchive / ht2_nicolive / nicolive-hamster- {ID de distribution} _main_ {16 nombre hexadécimal} / 4 / ts / playlist.m3u8? Avec la réponse de

playlist


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

(Omission)

Des données comme celles-ci ont été envoyées. Quand j'ai vérifié avec «.m3u8» et «.ts», il semble que ce soit un fichier utilisé par le protocole appelé «HLS (HTTP Live Streaming)». Ce HLS semble être facile à DL en utilisant ffmpeg, donc tout ce que vous avez à faire est d'obtenir l'URL.

Acquisition d'URL

Où avez-vous obtenu cette URL?

Appuyez sur "Ctrl + F" dans l'onglet "Réseau" de DevTools. Là, j'ai trouvé https: // {???}. Dmc.nico/hlsarchive/ht2_nicolive/nicolive-hamster-{Delivery ID} _main_ {16 nombre hexadécimal} Cherchons en tapant. Apparemment, ce qui sort est "communication vers URL", et "communication avec URL" est introuvable.

À la suite de divers essais tels que "Que voulez-vous dire?", Il semble que la communication dans le socket Web ne puisse pas être recherchée.

Alors, recherchez avec wss:. Ensuite, vous pouvez trouver 3 sockets Web. image.png

4012 / semble s'envoyer des données binaires. Laissez-le pour le moment. websocket reçoit les informations de discussion du serveur au format JSON. Vous pouvez également laisser cela.

Le socket Web restant (ici timeshift) n'est pas évident à première vue. Cherchons l'URL que vous recherchez. Puis il y a eu une communication à succès. image.png Il semble que cette communication reçoive JSON. J'ai trouvé l'URL que je cherchais dans ʻuri` dans ce JSON.

Are you still watching? Eh bien, c'est naturel de dire ça, mais il semble que l'URL devient invalide lorsque le serveur juge que "je n'ai plus vu ce type". Par conséquent, il n'est pas possible de dire "obtenir l'URL et la laisser tranquille".

Alors, que dois-je faire ... j'ai eu l'idée C'était une méthode pour «laisser Nico Nico». Si vous laissez l'URL de distribution ouverte dans Selenium, le client enverra automatiquement "Je suis toujours en train de regarder" au serveur. En attendant, ffmpeg sera DL, ce qui est une histoire simple. Cependant, cette méthode convient si vous venez de la télécharger, mais je pense qu'elle ne peut pas être utilisée quand il s'agit de "Je veux jouer avec ma propre application!", Donc dans ce cas, vous devez envoyer "Je suis toujours en train de regarder" par vous-même. Il y a. De plus, Nico Nico ne peut voir la livraison que dans une fenêtre à la fois (je ne connais pas Nico Nico, donc je ne connais pas les détails). Veuillez noter que si vous ouvrez la distribution pendant DL, DL s'arrêtera.

Ecrire un script en Python

code

shell


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

Vous pouvez le faire avec.

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()

Commentaire

paramètres de chrome

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')

Vous pouvez l'exécuter sans afficher la fenêtre en ajoutant l'option --headless. caps ['goog: loggingPrefs'] = {'performance': 'ALL'} est le paramètre pour voir le journal de communication. Je ne suis pas sûr des détails.

S'identifier

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()

Les étudiants Nico ne peuvent pas être vus sans se connecter. Par conséquent, connectez-vous ici une fois.

surveillance websocket

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

Vous pouvez obtenir le journal des performances avec driver.get_log ('performance'). ʻSi json.loads (i ['message']) ['message'] ['method'] == 'Network.webSocketFrameReceived'extrait uniquement la réception de websocket. De là, nous ne laisserons que ceux qui sont au "format JSON et qui ont un" corps "". L'URL est enregistrée avec ʻuri = i ['currentStream'] ['uri']. Ici, j'essaye d'obtenir la "meilleure URL de qualité" de la communication. Dans la partie commentaire, nous sélectionnons la vidéo de la plus haute qualité et arrêtons la vidéo. J'ai commenté cela car il est très probable que cela change dans les futures mises à jour. Nous vous recommandons d'étudier comment utiliser le sélénium et de l'écrire vous-même.

Sauvegarder et quitter

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

J'exécute ffmpeg avec subprocess.run (['ffmpeg', '- i', uri, '- c', 'copy', 'output.mp4']). ffmpeg doit déjà être installé. Fermez chrome avec'driver.quit () '.

finalement

Même si vous analysez un service Web et le publiez dans le monde entier, il devient souvent inutilisable en raison des modifications des spécifications du service. Cette fois, plutôt que de partager les "spécifications actuelles", j'ai voulu partager "N'est-il pas possible d'analyser les spécifications de cette manière?", Donc cet article a été rédigé ainsi. Comme je l'ai dit au début, je le partage sans rien savoir. Si vous pensez "est-ce étrange ici?", Veuillez le signaler.

Recommended Posts

Une histoire qui a analysé la livraison de Nico Nama.
Une histoire qui réduit l'effort de fonctionnement / maintenance
L'histoire de l'exportation d'un programme
L'histoire de la création d'un site qui répertorie les dates de sortie des livres
L'histoire du traitement A du blackjack (python)
L'histoire de la création d'un module qui ignore le courrier avec python
Une histoire qui visualise le présent de Qiita avec Qiita API + Elasticsearch + Kibana
L'histoire du développement d'une application WEB qui génère automatiquement des copies de capture [MeCab]
L'histoire de la création d'un package qui accélère le fonctionnement de Juman (Juman ++) & KNP
L'histoire de la création d'un générateur d'icônes mel
L'histoire de sys.path.append ()
L'histoire de la fabrication d'une boîte qui interconnecte la mémoire AL de Pepper et MQTT
L'histoire de la création d'une application Web qui enregistre des lectures approfondies avec Django
L'histoire de Django créant une bibliothèque qui pourrait être un peu plus utile
Créez un BOT qui raccourcit l'URL Discord
#Une fonction qui renvoie le code de caractère d'une chaîne de caractères
Une histoire qui a eu du mal avec l'ensemble commun HTTP_PROXY = ~
Générer cette forme du fond d'une bouteille pour animaux de compagnie
Une histoire sur le changement du nom principal de BlueZ
L'histoire selon laquelle la valeur de retour de tape.gradient () était None
Le problème Zip 4 Gbyte est une histoire du passé
[Python] Un programme qui compare les positions des kangourous.
L'histoire de la construction de Zabbix 4.4
Un outil qui transforme automatiquement le gacha de Soshage
L'histoire de la création d'un canal VIP dans le chatwork en interne
L'histoire du champ de modèle Django disparaissant de la classe
L'histoire de la création d'une base de données à l'aide de l'API Google Analytics
L'histoire de la création d'un bot de boîte à questions avec discord.py
Script Python qui compare le contenu de deux répertoires
Lors de l'incrémentation de la valeur d'une clé qui n'existe pas
Une histoire coincée avec l'installation de la bibliothèque de machine learning JAX
L'histoire selon laquelle la version de python 3.7.7 n'était pas adaptée à Heroku
L'histoire de Python et l'histoire de NaN
pandas Récupère le nom d'une colonne contenant un caractère spécifique
L'histoire de l'erreur de hachage est apparue lors de l'utilisation de Pipenv
Une formule qui calcule simplement l'âge à partir de la date de naissance
Une histoire qui a vérifié si le nombre de coronas augmente vraiment rapidement chez les jeunes
L'histoire de la création d'un pilote standard pour db avec python.
Une fonction qui mesure le temps de traitement d'une méthode en python
L'histoire du travail de sortie de l'application que Google ne raconte pas
L'histoire du "trou" dans le fichier
Une petite histoire addictive avec les permissions du répertoire spécifié par expdp (pour les débutants)
J'ai fait un bot mou qui m'informe de la température
L'histoire de la création d'un outil qui fonctionne sur Mac et Windows sur le site de développement de jeux
L'histoire du remontage du serveur d'application
[python] Une note que j'ai commencé à comprendre le comportement de matplotlib.pyplot
L'histoire de la création d'un Bot qui affiche les membres actifs dans un canal spécifique de Slack avec Python
[Python] Un programme qui fait pivoter le contenu de la liste vers la gauche
L'histoire de la création d'un slackbot qui génère un gif ou un png lorsque vous envoyez le code de traitement
[Pour les débutants chez AtCoder] Parlez de la quantité de calcul que vous voulez connaître approximativement
Une histoire sur la création d'un programme qui augmentera le nombre d'abonnés Instagram de 0 à 700 en une semaine
L'histoire d'un capteur de stationnement en 10 minutes avec le kit de démarrage GrovePi +
[Python] Un programme qui calcule le nombre de segments de chocolat qui remplissent les conditions
L'histoire de la création d'un robot LINE pour le petit-déjeuner d'une université de 100 yens avec Python
J'ai fait un calendrier qui met à jour automatiquement le calendrier de distribution de Vtuber
[Python] Un programme qui calcule le nombre de chaussettes jumelées