[PYTHON] Scraping à l'aide de lxml et enregistrement sur MySQL

Comme le titre l'indique, enregistrez les informations récupérées dans MySQL à l'aide de requêtes et de lxml. Comme il y avait peu de sites qui expliquaient de manière transversale, enregistrez-le sous forme de mémorandum. De toute évidence, le scraping met un fardeau sur le serveur de l'autre partie, il est donc nécessaire de générer des retards et de vérifier robot.txt.

environnement

Environnement d'exécution

Bibliothèque utilisée

--requests 2.13.0 - BibliothèqueHTTP --lxml 3.7.3 - Bibliothèque XML --PyMySQL 0.7.10 --Pilote MySQL --fake-useragent 0.1.5 --request_header creation library

D'autres bibliothèques couramment utilisées pour le scraping incluent urllib, htmllib, BeautifulSoup et Scrapy. Cependant, cette fois, je suis allé avec cette configuration. Puisque le faux-useragent n'était que dans pip, j'ai utilisé l'environnement pip cette fois.

Le grattage comme exemple

Grattage du titre d'article de l'article, de l'url du lien vers l'article individuel, du nom de la catégorie et de la date de création de l'article de la catégorie informatique de Yahoo News (http://news.yahoo.co.jp/list/?c=computer).

1.tiff

Quant à la structure de la page, chaque page d'index comporte 20 articles individuels et la page d'index elle-même compte 714 pages. La structure URL de la page d'index est http://news.yahoo.co.jp/list/?c=computer&p=1, comme http://news.yahoo.co.jp/list/?c=computer&p= C'est celui avec le numéro ajouté. Chaque article individuel est divisé en trois types: les articles avec vignettes, les articles sans vignettes et les articles supprimés.

Exemple d'article avec vignette 2.tiff

Exemple d'article sans vignette 3.tiff

Exemple d'article supprimé 4.tiff

Comment obtenir xPath

lxml utilise le format xPath pour spécifier l'emplacement que vous souhaitez analyser. Utilisez la fonction de vérification de chrome pour obtenir le xPath des informations que vous souhaitez obtenir. Accédez à la page d'index dans Chrome et sélectionnez Valider dans le menu contextuel. Après cela, ouvrez l'onglet html tout en vous référant aux points forts de la page. Lorsque vous atteignez enfin les informations que vous souhaitez extraire, vous pouvez sélectionner ces informations, afficher le menu contextuel et sélectionner Copier> Copier XPath pour obtenir le xPath.

5.tiff

Exemple XPath obtenu en fonction de la classification d'articles individuels

--Exemple d'article avec vignette (premier à partir du haut) --Titre de l'article: // * [@ id = "main"] / div [1] / div [1] / div [4] / ul / li [1] / a / span [2]

Considérez la régularité xPath de chaque élément d'information à partir d'un échantillon de xPath collecté en fonction de la division des articles individuels.

--La position des articles individuels est spécifiée par li [num] à la position numérique à partir du haut. --Span [num] immédiatement après / a / passe à 2,3,3,1,2,2 selon la présence ou l'absence de vignettes. --Pour les articles supprimés, la partie / a devient / div.

Etc. sera considéré. Sur la base de cette considération, nous allons générer le xPath réel et le transmettre à lxml.

La partie de code réelle et comment utiliser chaque bibliothèque

requests,fake-useragent

import requests  #Obtenir le code HTML par demande GET à l'URL
from fake_useragent import UserAgent  #Générer un en-tête

def make_scraping_url(page_num):
    scraping_url = "http://news.yahoo.co.jp/list/?c=computer&p=" + str(page_num)
    return scraping_url

counter_200 = 0
counter_404 = 0

def make_html_text(scraping_url):
    global counter_200
    global counter_404

    ua = UserAgent()  # fakeuser-objet agent
    ran_header = {"User-Agent": ua.random}  #Générer un en-tête aléatoire à chaque fois qu'il est appelé
    html = requests.get(scraping_url, headers=ran_header)  #objet html obtenu par requêtes
    html_text = html.text

    if html.status_code == 200:
        counter_200 += 1
    elif html.status_code == 400:
        counter_404 += 1

    return html_text

L'en-tête des requêtes est généré aléatoirement par fake_useragent. Préparez counter_200 comme compteur lorsque la demande d'obtention des demandes est réussie et counter_404 comme variable globale comme compteur lorsque le traitement final et la demande d'obtention échouent. lxml

import lxml.html  #analyseur XML

#Généré en convertissant le modèle d'objet de document et le format HTML au format xml
def make_dom(html_text):
    dom = lxml.html.fromstring(html_text)
    return dom

#Reçoit dom et xpath et renvoie du texte
def make_text_from_xpath(dom, xpath):
    text = dom.xpath(xpath)[0].text
    return text

#Prend dom et xpath et renvoie le lien
def make_link_from_xpath(dom, xpath):
    link = dom.xpath(xpath)[0].attrib["href"]
    return link

#Le texte que vous souhaitez obtenir est du texte()S'il est inclus dans
def make_text_from_xpath_function(dom, xpath):
    text = dom.xpath(xpath)[1]
    return text

Vous pouvez créer un DOM (Document Object Model) en passant html au format texte à lxml. Vous pouvez obtenir des informations sur chacun d'eux en transmettant XPath à cet objet. La méthode de spécification de XPath diffère selon le type d'informations. Il semble bon de vérifier à chaque fois la méthode de description XPath. Si vous passez XPath, il sera essentiellement retourné au format liste, vous devez donc vérifier où se trouvent les informations dans la liste en tournant une boucle for, etc.

Exemple de boucle For pour trouver les informations que vous souhaitez obtenir à partir du format de liste

def debug_check_where_link_number(page_num, article_num):
    scraping_url = make_scraping_url(page_num)
    html_text = make_html_text(scraping_url)
    dom = make_dom(html_text)
    xpath = Xpath(article_num)

    thumbnail_link = check_thumbnail_link(dom, article_num)
    if thumbnail_link[0] is True and thumbnail_link[1] is True:
        xpath.with_thumbnail()
    elif thumbnail_link[0] is False:
        xpath.no_thumbnail()
    elif thumbnail_link[1] is False:
        xpath.deleted()

    get_link = make_link_from_xpath(dom, xpath.info_list[1])
    l_get_link = get_link.split("/")
    counter = 0
    for item in l_get_link:
        print(counter)
        counter += 1
        print(item)

miniature_link sera expliqué un peu plus bas.

Classe XPath

#Une classe qui résume les informations que vous souhaitez gratter et leurs xpaths, passez d'abord le numéro de l'article individuel par le haut
class Xpath:
    def __init__(self, article_num):
        self.article_num = article_num
        self.info_list = []

#Pour les articles avec des miniatures
    def with_thumbnail(self):
        self.info_list = ["//*[@id=\"main\"]/div[1]/div[1]/div[4]/ul/li[" + str(self.article_num) + "]/a/span[2]",  # title
                          "//*[@id=\"main\"]/div[1]/div[1]/div[4]/ul/li[" + str(self.article_num) + "]/a", # link
                          "//*[@id=\"main\"]/div[1]/div[1]/div[4]/ul/li[" + str(self.article_num) + "]/a/span[3]/span[1]", # category
                          "//*[@id=\"main\"]/div[1]/div[1]/div[4]/ul/li[" + str(self.article_num) + "]/a/span[3]/span[2]"] # timestamp

    #Pour les articles sans vignettes
    def no_thumbnail(self):
        self.info_list = ["//*[@id=\"main\"]/div[1]/div[1]/div[4]/ul/li[" + str(self.article_num) + "]/a/span[1]",  # title
                          "//*[@id=\"main\"]/div[1]/div[1]/div[4]/ul/li[" + str(self.article_num) + "]/a", # link
                          "//*[@id=\"main\"]/div[1]/div[1]/div[4]/ul/li[" + str(self.article_num) + "]/a/span[2]/span[1]", # category
                          "//*[@id=\"main\"]/div[1]/div[1]/div[4]/ul/li[" + str(self.article_num) + "]/a/span[2]/span[2]"] # timestamp

    #Pour les articles supprimés
    def deleted(self):
        self.info_list = ["//*[@id=\"main\"]/div[1]/div[1]/div[4]/ul/li[" + str(self.article_num) + "]/div/span[1]",  # title
                          None,  # link
                          "//*[@id=\"main\"]/div[1]/div[1]/div[4]/ul/li[" + str(self.article_num) + "]/div/span[2]/span[1]", # category
                          "//*[@id=\"main\"]/div[1]/div[1]/div[4]/ul/li[" + str(self.article_num) + "]/div/span[2]/span[2]"]  # timestamp

Créez une classe XPath basée sur la discussion XPath que nous avons faite précédemment.

Classement distinctif des articles

#Déterminer s'il existe une miniature / un lien pour un article individuel
def check_thumbnail_link(dom, article_num):
    #Un tableau montrant la présence ou l'absence de vignettes et de liens
    thumbnail_link = [True, True]

    #Lancer le xpath de la catégorie d'article sans vignettes et obtenir une erreur si les vignettes existent
    try:  #Ce n'est pas une erreur, c'est-à-dire que la vignette n'existe pas
        make_text_from_xpath(dom, "//*[@id=\"main\"]/div[1]/div[1]/div[4]/ul/li[" + str(article_num) + "]/a/span[2]/span[1]")
        thumbnail_link[0] = False
    except IndexError:  #Erreur, c'est-à-dire qu'une vignette existe
        pass

    #Lancer le xpath du lien de l'article avec la vignette, et si le lien n'existe pas, une erreur sera renvoyée
    try:  #Aucune erreur, c'est-à-dire que le lien existe
        make_link_from_xpath(dom, "//*[@id=\"main\"]/div[1]/div[1]/div[4]/ul/li[" + str(article_num) + "]/a")
    except IndexError:  #Erreur, c'est-à-dire que le lien n'existe pas
        thumbnail_link[1] = False

    return thumbnail_link

Essayez de lancer un XPath et déterminez s'il renvoie une erreur. C'est tout à fait une compétence, alors je pense à une meilleure façon. PyMySQL

Préparation côté MySQL et instructions MySQL fréquemment utilisées

$ mysql.server start #Démarrer le serveur MySQL
$ mysql -u root -p #Lancer le curseur MySQL

mysql> show databases; #Vérifiez la base de données
mysql> create database database_name #Créer une base de données
mysql> use database_name; #Sélectionnez la base de données à utiliser
mysql> show tables; #Vérification de la table dans la base de données
mysql> show columns from table_name; #Vérifiez les colonnes du tableau
mysql> select * from table_name; #Afficher tous les éléments du tableau
mysql> delete from table_name; #Supprimer tous les éléments du tableau, utiliser en cas de mauvais grattage
mysql> create table table_name(column_name_1 column_type_1, column_name_2 column_type_1, ...); #Créer une table

#Table à créer cette fois
mysql> create table yahoo_news(page_num int not null,
                               title varchar(255),
                               link_num varchar(255),
                               category varchar(255),
                               article_time varchar(255));

J'ai créé une base de données nommée scraping et une table nommée yahoo_news. La raison pour laquelle link_num est de type varchar est que si link_num commence par 0, 0 disparaît quand il est mis dans le type int.

Traitement côté python

import pymysql.cursors  #Client MySQL

#Informations de connexion MySQL
connection = pymysql.connect(host="localhost",
                             user="root",
                             password="hogehoge",
                             db="scraping",
                             charset="utf8")

#Un curseur pour lancer une requête et chaque instruction
cur = connection.cursor()
sql_insert = "INSERT INTO yahoo_news VALUES (%s, %s, %s, %s, %s)"   #Faire correspondre le nombre de chaînes de substitution avec le nombre de colonnes
sql_check = "SELECT * FROM yahoo_news WHERE page_num = %s"

# page_Vérifiez num et ignorez s'il est inclus dans la base de données
cur.execute(sql_check, page_num)
check_num = cur.fetchall()  #Obtenez les informations qui sont revenues
if check_num:   #Lorsqu'il ne s'agit pas d'une chaîne vide, c'est-à-dire lorsqu'il est inclus dans la base de données
    continue

#Transmettre des informations pour 20 éléments à MySQL
cur.executemany(sql_insert, l_all_get_text)  #exécuter plusieurs lors du passage d'une liste d'inclusions de tuple, exécuter lors du passage d'un simple tuple
connection.commit()  #Si vous ne vous engagez pas, l'insertion ne sera pas effectuée

#Arrêt du curseur et de la base de données
cur.close()
connection.close()

L'utilisation de la chaîne de substitution «$ s» facilite la création d'instructions. Il était étonnamment bon d'utiliser ʻexecute lors du passage du type int, du type str et du type tuple, et ʻexecutemany lors du passage du type liste.

Tout le code qui résume chacun

# coding: utf-8
import requests  #Obtenir le code HTML par demande GET à l'URL
from fake_useragent import UserAgent  #Générer un en-tête
import lxml.html  #analyseur XML
import pymysql.cursors  #Client MySQL
import time  #Pour la génération de retard


def make_scraping_url(page_num):
    scraping_url = "http://news.yahoo.co.jp/list/?c=computer&p=" + str(page_num)
    return scraping_url


counter_200 = 0
counter_404 = 0


def make_html_text(scraping_url):
    global counter_200
    global counter_404

    ua = UserAgent()  # fakeuser-objet agent
    ran_header = {"User-Agent": ua.random}  #Générer un en-tête aléatoire à chaque fois qu'il est appelé
    html = requests.get(scraping_url, headers=ran_header)  #objet html obtenu par requêtes
    html_text = html.text

    if html.status_code == 200:
        counter_200 += 1
    elif html.status_code == 400:
        counter_404 += 1

    return html_text


#Généré en convertissant le modèle d'objet de document et le format HTML au format xml
def make_dom(html_text):
    dom = lxml.html.fromstring(html_text)
    return dom


#Reçoit dom et xpath et renvoie du texte
def make_text_from_xpath(dom, xpath):
    text = dom.xpath(xpath)[0].text
    return text


#Prend dom et xpath et renvoie le lien
def make_link_from_xpath(dom, xpath):
    link = dom.xpath(xpath)[0].attrib["href"]
    return link


#Le texte que vous souhaitez obtenir est du texte()S'il est inclus dans
def make_text_from_xpath_function(dom, xpath):
    text = dom.xpath(xpath)[1]
    return text


#Une classe qui résume les informations que vous souhaitez gratter et leurs xpaths, passez d'abord le numéro de l'article individuel par le haut
class Xpath:
    def __init__(self, article_num):
        self.article_num = article_num
        self.info_list = []


#Pour les articles avec des miniatures
    def with_thumbnail(self):
        self.info_list = ["//*[@id=\"main\"]/div[1]/div[1]/div[4]/ul/li[" + str(self.article_num) + "]/a/span[2]",  # title
                          "//*[@id=\"main\"]/div[1]/div[1]/div[4]/ul/li[" + str(self.article_num) + "]/a", # link
                          "//*[@id=\"main\"]/div[1]/div[1]/div[4]/ul/li[" + str(self.article_num) + "]/a/span[3]/span[1]", # category
                          "//*[@id=\"main\"]/div[1]/div[1]/div[4]/ul/li[" + str(self.article_num) + "]/a/span[3]/span[2]"] # timestamp

    #Pour les articles sans vignettes
    def no_thumbnail(self):
        self.info_list = ["//*[@id=\"main\"]/div[1]/div[1]/div[4]/ul/li[" + str(self.article_num) + "]/a/span[1]",  # title
                          "//*[@id=\"main\"]/div[1]/div[1]/div[4]/ul/li[" + str(self.article_num) + "]/a", # link
                          "//*[@id=\"main\"]/div[1]/div[1]/div[4]/ul/li[" + str(self.article_num) + "]/a/span[2]/span[1]", # category
                          "//*[@id=\"main\"]/div[1]/div[1]/div[4]/ul/li[" + str(self.article_num) + "]/a/span[2]/span[2]"] # timestamp

    #Pour les articles supprimés
    def deleted(self):
        self.info_list = ["//*[@id=\"main\"]/div[1]/div[1]/div[4]/ul/li[" + str(self.article_num) + "]/div/span[1]",  # title
                          None,  # link
                          "//*[@id=\"main\"]/div[1]/div[1]/div[4]/ul/li[" + str(self.article_num) + "]/div/span[2]/span[1]", # category
                          "//*[@id=\"main\"]/div[1]/div[1]/div[4]/ul/li[" + str(self.article_num) + "]/div/span[2]/span[2]"]  # timestamp


#Déterminer s'il existe une miniature / un lien pour un article individuel
def check_thumbnail_link(dom, article_num):
    #Un tableau montrant la présence ou l'absence de vignettes et de liens
    thumbnail_link = [True, True]

    #Lancer le xpath de la catégorie d'article sans vignettes et obtenir une erreur si les vignettes existent
    try:  #Ce n'est pas une erreur, c'est-à-dire que la vignette n'existe pas
        make_text_from_xpath(dom, "//*[@id=\"main\"]/div[1]/div[1]/div[4]/ul/li[" + str(article_num) + "]/a/span[2]/span[1]")
        thumbnail_link[0] = False
    except IndexError:  #Erreur, c'est-à-dire qu'une vignette existe
        pass

    #Lancer le xpath du lien de l'article avec la vignette, et si le lien n'existe pas, une erreur sera renvoyée
    try:  #Aucune erreur, c'est-à-dire que le lien existe
        make_link_from_xpath(dom, "//*[@id=\"main\"]/div[1]/div[1]/div[4]/ul/li[" + str(article_num) + "]/a")
    except IndexError:  #Erreur, c'est-à-dire que le lien n'existe pas
        thumbnail_link[1] = False

    return thumbnail_link


#opération d'exploration, dom et page_num et article_Reçoit num et renvoie un tuple résumant les informations de scraping
def crawling(dom, page_num, article_num):
    list_get_text = []  #Liste de collecte d'informations pour un article, le type de tuple est utilisé pour l'insertion dans MySQL, donc convertissez-le plus tard
    list_get_text.append(page_num)
    #Créer un objet Xpath
    xpath = Xpath(article_num)

    #Vérifiez les vignettes et les liens, les informations_Générer une liste
    thumbnail_link = check_thumbnail_link(dom, article_num)
    if thumbnail_link[0] is True and thumbnail_link[1] is True:
        xpath.with_thumbnail()
    elif thumbnail_link[0] is False:
        xpath.no_thumbnail()
    elif thumbnail_link[1] is False:
        xpath.deleted()

    for index, xpath_info in enumerate(xpath.info_list):
        if index == 1 and thumbnail_link[1] is True:    #Pour lien
            get_link = make_link_from_xpath(dom, xpath_info)
            l_get_link = get_link.split("/")
            list_get_text.append(l_get_link[4])  #L'index du lien ici est en fait un débogage_check_where_link_number()Découvrez dans
        elif index == 1 and thumbnail_link[1] is False:
            list_get_text.append(None)  #NULL dans MySQL est passé en tant que type None
        else:   #Pour le texte
            get_text = make_text_from_xpath(dom, xpath_info)
            list_get_text.append(get_text)

    tuple_get_text = tuple(list_get_text)

    return tuple_get_text


#Informations de connexion MySQL
connection = pymysql.connect(host="localhost",
                             user="root",
                             password="hogehoge",
                             db="scraping",
                             charset="utf8")

#Un curseur pour lancer une requête et chaque instruction
cur = connection.cursor()
sql_insert = "INSERT INTO yahoo_news VALUES (%s, %s, %s, %s, %s)"   #Faire correspondre le nombre de chaînes de substitution avec le nombre de colonnes
sql_check = "SELECT * FROM yahoo_news WHERE page_num = %s"


def main():
    global counter_200

    start_page = 1
    end_page = 5
    for page_num in range(start_page, end_page + 1):
        # page_Vérifiez num et ignorez s'il est inclus dans la base de données
        cur.execute(sql_check, page_num)
        check_num = cur.fetchall()  #Obtenez les informations qui sont revenues
        if check_num:   #Lorsqu'il ne s'agit pas d'une chaîne vide, c'est-à-dire lorsqu'il est inclus dans la base de données
            continue

        #Terminer le traitement
        if counter_404 == 5:
            break

        l_all_get_text = []  #Liste de collecte d'informations pour 20 articles

        #Diverses générations et accès à l'url se font également ici
        scraping_url = make_scraping_url(page_num)
        html_text = make_html_text(scraping_url)
        dom = make_dom(html_text)

        for article_num in range(1, 21):
            tuple_get_text = crawling(dom, page_num, article_num)
            l_all_get_text.append(tuple_get_text)

        #Transmettre des informations pour 20 éléments à MySQL
        cur.executemany(sql_insert, l_all_get_text)  #exécuter plusieurs lors du passage d'une liste d'inclusions de tuple, exécuter lors du passage d'un simple tuple
        connection.commit()  #Si vous ne vous engagez pas, l'insertion ne sera pas effectuée

        #Génération de retard pour chaque exploration
        time.sleep(3)   #Spécifié en secondes

        #Retarder la génération pour chaque exploration plusieurs fois
        if counter_200 == 10:
            counter_200 = 0
            time.sleep(60)  #Spécifié en secondes

    #Arrêt du curseur et de la base de données
    cur.close()
    connection.close()


def debug_check_where_link_number(page_num, article_num):
    scraping_url = make_scraping_url(page_num)
    html_text = make_html_text(scraping_url)
    dom = make_dom(html_text)
    xpath = Xpath(article_num)

    thumbnail_link = check_thumbnail_link(dom, article_num)
    if thumbnail_link[0] is True and thumbnail_link[1] is True:
        xpath.with_thumbnail()
    elif thumbnail_link[0] is False:
        xpath.no_thumbnail()
    elif thumbnail_link[1] is False:
        xpath.deleted()

    get_link = make_link_from_xpath(dom, xpath.info_list[1])
    l_get_link = get_link.split("/")
    counter = 0
    for item in l_get_link:
        print(counter)
        counter += 1
        print(item)


if __name__ == "__main__":
    main()

finalement

Si quoi que ce soit, il est devenu une mise en œuvre pour créer un plan de site dans le site. Il semble que chaque page individuelle devrait être accédée en utilisant le link_num obtenu par ce script. Il serait utile que vous signaliez des erreurs ou des améliorations.

Recommended Posts

Scraping à l'aide de lxml et enregistrement sur MySQL
J'ai essayé d'obtenir les informations du Web en utilisant "Requests" et "lxml"
Scraping, prétraitement et écriture dans postgreSQL
Connectez-vous à MySQL à l'aide de Flask SQL Alchemy
[EC2] Introduction au grattage avec du sélénium (extraction de texte et capture d'écran)
De Python à l'utilisation de MeCab (et CaboCha)
Vider les données SQLite3 et migrer vers MySQL
L'histoire de l'abandon d'essayer de se connecter à MySQL en utilisant Heroku
Grattage de la nourriture avec python et sortie en CSV
J'ai essayé le web scraping en utilisant python et sélénium
Trois choses auxquelles j'étais accro lors de l'utilisation de Python et MySQL avec Docker
Connectez-vous à mysql
Scraping à l'aide de Python
Recherche de balises pixiv et enregistrement d'illustrations à l'aide de Python
Grattage 2 Comment gratter
Premiers pas avec le Web Scraping
Essayez de le faire avec GUI, PyQt en Python
Comment ajouter de nouvelles données (lignes droites et tracés) à l'aide de matplotlib
Exploration Web, scraping Web, acquisition de caractères et sauvegarde d'image avec python