web-dev-qa-db-ja.com

BeautifulSoupとPythonで複数のページをスクレイプ

私のコードは、tr align = centerタグを[ http://my.gwu.edu/mod/pws/courses.cfm?campId=1&termId=201501&subjId=ACCY ]から正常にスクレイピングし、td要素を書き込みますテキストファイルに。

ただし、上記のサイトには複数のページがあり、それらをスクレイピングできるようにしたいと考えています。

たとえば、上記のURLで「ページ2」へのリンクをクリックしても、全体のURLは変わりません。ページのソースを見て、次のページに進むためのJavaScriptコードを見つけました。

リストにある利用可能なすべてのページからデータを取得するようにコードを変更するにはどうすればよいですか?

ページ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')
13
Philip McQuitty

ここでのコツは、他のページを表示するためにリンクをクリックしたときに、ページ変更アクションに出入りするリクエストをチェックすることです。これを確認する方法は、Chromeの検査ツールを使用することです F12)またはFirefoxでFirebug拡張機能をインストールします。この回答では、Chromeの検査ツールを使用します。私の設定については以下をご覧ください。

enter image description here

今、私たちが見たいのは、別のページへのGETリクエスト、またはページを変更するPOSTリクエストです。ツールが開いている間に、ページ番号をクリックします。ほんの短い間、表示されるリクエストは1つだけで、それはPOSTメソッドです。他のすべての要素はすぐにページに沿って表示されます。探しているものについては以下をご覧ください。

enter image description here

上記のPOSTメソッドをクリックします。タブのある種類のサブウィンドウが表示されます。 Headersタブをクリックします。このページには、リクエストヘッダーが表示されます。これは、相手側(サイトなど)が接続するために必要な識別情報です(他の誰かが私よりもずっとこのことを説明できます)。

URLにページ番号、ロケーションマーカー、カテゴリなどの変数が含まれている場合は常に、そうではない場合、サイトはクエリ文字列を使用します。要するに、SQLクエリ(実際には、SQLクエリである場合もあります)に似ており、サイトで必要な情報を引き出すことができます。この場合、クエリ文字列パラメーターの要求ヘッダーを確認できます。少し下にスクロールすると、見つかるはずです。

enter image description here

ご覧のとおり、クエリ文字列パラメーターはURLの変数と一致します。少し下に、Form Dataとその下にpageNum: 2が表示されます。これが鍵です。

POSTリクエストは、フォームの送信、ウェブサイトへのログインなどの際に行われる種類のリクエストであるため、一般的にフォームリクエストとして知られています。基本的に、情報を送信する必要のあるものはほとんど何でも。ほとんどの人には見られないのは、POSTリクエストには自分が従うURLがあることです。これの良い例は、あなたがウェブサイトにログインし、非常に簡単に、/index.htmlなどに落ち着く前に、アドレスバーが何らかの意味不明なURLに変化するのを見るときです。

上記の段落が基本的に意味するのは、URLにフォームデータを追加できる(ただし常にではない)ことで、実行時にPOSTリクエストが実行されるということです。追加する必要のある正確な文字列を知るには、view sourceをクリックします。

enter image description here

URLに追加して、機能するかどうかをテストします。

enter image description here

出来上がり、それは動作します。さて、最後の課題は、最後のページを自動的に取得し、すべてのページをスクレイピングすることです。あなたのコードはほとんどそこにあります。あとは、ページ数を取得し、スクレイプするURLのリストを作成し、それらを繰り返し処理するだけです。

変更されたコードは次のとおりです。

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

正規表現を使用して適切なリンクを取得します。次に、リストの内包表記を使用して、URL文字列のリストを作成しました。最後に、それらについて繰り返します。

結果:

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

お役に立てば幸いです。

編集:

まったくの退屈から、クラスディレクトリ全体のスクレーパーを作成したと思います。また、使用可能なページが1つしかない場合にエラーが発生しないように、上記のコードと以下のコードの両方を更新します。

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')
42
Jerome Montino