web-dev-qa-db-fra.com

Grattez plusieurs pages avec BeautifulSoup et Python

Mon code a réussi à gratter les balises tr align = center de [ http://my.gwu.edu/mod/pws/courses.cfm?campId=1&termId=201501&subjId=ACCY ] et écrit les éléments td dans un fichier texte.

Cependant, il y a plusieurs pages disponibles sur le site ci-dessus dans lesquelles je voudrais pouvoir gratter.

Par exemple, avec l'URL ci-dessus, lorsque je clique sur le lien vers "page 2", l'URL globale ne change PAS. J'ai regardé la source de la page et j'ai vu un code javascript pour passer à la page suivante.

Comment modifier mon code pour extraire les données de toutes les pages répertoriées disponibles?

Mon code qui fonctionne uniquement pour la page 1:

import bs4
import requests 

response = requests.get('http://my.gwu.edu/mod/pws/courses.cfm?campId=1&termId=201501&subjId=ACCY')

soup = bs4.BeautifulSoup(response.text)
soup.prettify()

acct = open("/Users/it/Desktop/accounting.txt", "w")

for tr in soup.find_all('tr', align='center'):
    stack = []
    for td in tr.findAll('td'):
        stack.append(td.text.replace('\n', '').replace('\t', '').strip())

    acct.write(", ".join(stack) + '\n')
14
Philip McQuitty

L'astuce consiste à vérifier les demandes qui entrent et sortent de l'action de changement de page lorsque vous cliquez sur le lien pour afficher les autres pages. La façon de vérifier cela consiste à utiliser l'outil d'inspection de Chrome (en appuyant sur F12) ou en installant l'extension Firebug dans Firefox. J'utiliserai l'outil d'inspection de Chrome dans cette réponse. Voir ci-dessous pour mes paramètres.

enter image description here

Maintenant, ce que nous voulons voir, c'est soit une demande GET vers une autre page, soit une demande POST qui change la page. Pendant que l'outil est ouvert, cliquez sur un numéro de page. Pour un très bref instant, il n'y aura qu'une seule requête qui apparaîtra, et c'est une méthode POST. Tous les autres éléments suivront et rempliront rapidement la page. Voir ci-dessous pour ce que nous recherchons.

enter image description here

Cliquez sur la méthode POST ci-dessus. Il devrait faire apparaître une sous-fenêtre de sortes qui a des onglets. Cliquez sur l'onglet Headers. Cette page répertorie les en-têtes de demande, à peu près les éléments d'identification dont l'autre côté (le site, par exemple) a besoin de vous pour pouvoir vous connecter (quelqu'un d'autre peut expliquer ce muuuch mieux que moi).

Chaque fois que l'URL contient des variables telles que des numéros de page, des marqueurs d'emplacement ou des catégories, le plus souvent, le site utilise des chaînes de requête. Pour faire court, c'est similaire à une requête SQL (en fait, c'est une requête SQL, parfois) qui permet au site d'extraire les informations dont vous avez besoin. Si tel est le cas, vous pouvez vérifier les en-têtes de demande pour les paramètres de chaîne de requête. Faites défiler un peu et vous devriez le trouver.

enter image description here

Comme vous pouvez le voir, les paramètres de la chaîne de requête correspondent aux variables de notre URL. Un peu plus bas, vous pouvez voir Form Data avec pageNum: 2 dessous. Telle est la clé.

POST les demandes sont plus communément appelées demandes de formulaire, car il s'agit du type de demandes faites lorsque vous soumettez des formulaires, vous connectez à des sites Web, etc. Ce que la plupart des gens ne voient pas, c'est que les requêtes POST ont une URL qu'elles suivent. Un bon exemple de cela est lorsque vous vous connectez à un site Web et, très brièvement, voyez votre barre d'adresse se transformer en une sorte d'URL de charabia avant de vous installer sur /index.html ou quelque chose.

Ce que signifie le paragraphe ci-dessus, c'est que vous pouvez (mais pas toujours) ajouter les données du formulaire à votre URL et il exécutera pour vous la requête POST lors de l'exécution. Pour connaître la chaîne exacte que vous devez ajouter, cliquez sur view source.

enter image description here

Testez si cela fonctionne en l'ajoutant à l'URL.

enter image description here

Et voila, ça marche. Maintenant, le vrai défi: obtenir automatiquement la dernière page et gratter toutes les pages. Votre code est à peu près là. Les seules choses qui restent à faire sont d'obtenir le nombre de pages, de construire une liste d'URL à gratter et de les parcourir.

Le code modifié est ci-dessous:

from bs4 import BeautifulSoup as bsoup
import requests as rq
import re

base_url = 'http://my.gwu.edu/mod/pws/courses.cfm?campId=1&termId=201501&subjId=ACCY'
r = rq.get(base_url)

soup = bsoup(r.text)
# Use regex to isolate only the links of the page numbers, the one you click on.
page_count_links = soup.find_all("a",href=re.compile(r".*javascript:goToPage.*"))
try: # Make sure there are more than one page, otherwise, set to 1.
    num_pages = int(page_count_links[-1].get_text())
except IndexError:
    num_pages = 1

# Add 1 because Python range.
url_list = ["{}&pageNum={}".format(base_url, str(page)) for page in range(1, num_pages + 1)]

# Open the text file. Use with to save self from grief.
with open("results.txt","wb") as acct:
    for url_ in url_list:
        print "Processing {}...".format(url_)
        r_new = rq.get(url_)
        soup_new = bsoup(r_new.text)
        for tr in soup_new.find_all('tr', align='center'):
            stack = []
            for td in tr.findAll('td'):
                stack.append(td.text.replace('\n', '').replace('\t', '').strip())
            acct.write(", ".join(stack) + '\n')

Nous utilisons des expressions régulières pour obtenir les liens appropriés. Ensuite, en utilisant la compréhension de la liste, nous avons construit une liste de chaînes URL. Enfin, nous les parcourons.

Résultats:

Processing http://my.gwu.edu/mod/pws/courses.cfm?campId=1&termId=201501&subjId=ACCY&pageNum=1...
Processing http://my.gwu.edu/mod/pws/courses.cfm?campId=1&termId=201501&subjId=ACCY&pageNum=2...
Processing http://my.gwu.edu/mod/pws/courses.cfm?campId=1&termId=201501&subjId=ACCY&pageNum=3...
[Finished in 6.8s]

enter image description here

J'espère que cela pourra aider.

MODIFIER:

Par pur ennui, je pense que je viens de créer un grattoir pour l'ensemble du répertoire de classe. De plus, je mets à jour les codes ci-dessus et ci-dessous pour ne pas faire d'erreur lorsqu'il n'y a qu'une seule page disponible.

from bs4 import BeautifulSoup as bsoup
import requests as rq
import re

spring_2015 = "http://my.gwu.edu/mod/pws/subjects.cfm?campId=1&termId=201501"
r = rq.get(spring_2015)
soup = bsoup(r.text)
classes_url_list = [c["href"] for c in soup.find_all("a", href=re.compile(r".*courses.cfm\?campId=1&termId=201501&subjId=.*"))]
print classes_url_list

with open("results.txt","wb") as acct:
    for class_url in classes_url_list:
        base_url = "http://my.gwu.edu/mod/pws/{}".format(class_url)
        r = rq.get(base_url)

        soup = bsoup(r.text)
        # Use regex to isolate only the links of the page numbers, the one you click on.
        page_count_links = soup.find_all("a",href=re.compile(r".*javascript:goToPage.*"))
        try:
            num_pages = int(page_count_links[-1].get_text())
        except IndexError:
            num_pages = 1

        # Add 1 because Python range.
        url_list = ["{}&pageNum={}".format(base_url, str(page)) for page in range(1, num_pages + 1)]

        # Open the text file. Use with to save self from grief.
        for url_ in url_list:
            print "Processing {}...".format(url_)
            r_new = rq.get(url_)
            soup_new = bsoup(r_new.text)
            for tr in soup_new.find_all('tr', align='center'):
                stack = []
                for td in tr.findAll('td'):
                    stack.append(td.text.replace('\n', '').replace('\t', '').strip())
                acct.write(", ".join(stack) + '\n')
45
Jerome Montino