1

書籍の翻訳に関する情報を含むユネスコの Web サイトから書籍データをスクレイピングする簡単な小さなプログラムを作成しました。コードは私が望んでいることを実行していますが、約 20 か国で処理されるまでに、約 6 GB の RAM を使用しています。処理する必要があるのは約 200 であるため、これではうまくいきません。

すべての RAM 使用量がどこから来ているのかわからないため、それを減らす方法がわかりません。すべての本の情報を保持しているのは辞書だと思いますが、私は確信が持てません。多くの国を処理するのではなく、単にプログラムを国ごとに 1 回実行する必要があるかどうかわかりません。または、それを行うためのより良い方法があれば?

このようなものを書いたのはこれが初めてであり、私はかなり初心者で独学のプログラマーなので、コードの重大な欠陥や、質問に直接関係のない改善のヒントを指摘してください。手元に。

これは私のコードです。ご協力いただきありがとうございます。

from __future__ import print_function
import urllib2, os
from bs4 import BeautifulSoup, SoupStrainer

''' Set list of countries and their code for niceness in explaining what
is actually going on as the program runs. '''
countries = {"AFG":"Afghanistan","ALA":"Aland Islands","DZA":"Algeria"}

'''List of country codes since dictionaries aren't sorted in any
way, this makes processing easier to deal with if it fails at
some point, mid run.'''
country_code_list = ["AFG","ALA","DZA"]

base_url = "http://www.unesco.org/xtrans/bsresult.aspx?lg=0&c="
destination_directory = "/Users/robbie/Test/"
only_restable = SoupStrainer(class_="restable")

class Book(object):
    def set_author(self,book): 
        '''Parse the webpage to find author names. Finds last name, then
        first name of original author(s) and sets the Book object's 
        Author attribute to the resulting string.'''

        authors = ""
        author_last_names = book.find_all('span',class_="sn_auth_name")
        author_first_names = book.find_all('span', attrs={\
            'class':"sn_auth_first_name"})
        if author_last_names == []: self.Author = [" "]

        for author in author_last_names:
            try: 
                first_name = author_first_names.pop()
                authors = authors + author.getText() + ', ' + \
                    first_name.getText()

            except IndexError:
                authors = authors + (author.getText())
        self.author = authors

    def set_quality(self,book):
        ''' Check to see if book page is using Quality, then set it if 
        so.'''

        quality = book.find_all('span', class_="sn_auth_quality")

        if len(quality) == 0: self.quality = " "

        else: self.quality = quality[0].contents[0]

    def set_target_title(self,book): 
        target_title = book.find_all('span', class_="sn_target_title")
        if len(target_title) == 0: self.target_title = " "
        else: self.target_title = target_title[0].contents[0]

    def set_target_language(self,book): 
        target_language = book.find_all('span', class_="sn_target_lang")
        if len(target_language) == 0: self.target_language = " "
        else: self.target_language = target_language[0].contents[0]

    def set_translator_name(self,book) : 
        translators = ""
        translator_last_names = book.find_all('span', class_="sn_transl_name")
        translator_first_names = book.find_all('span', \
                                               class_="sn_transl_first_name")
        if translator_first_names == [] and translator_last_names == [] :
            self.translators = " "
            return None

        for translator in translator_last_names:
            try: 
                first_name = translator_first_names.pop()
                translators = translators + \
                    (translator.getText() + ',' \
                     + first_name.getText())
            except IndexError:
                translators = translators + \
                    (translator.getText())

        self.translators = translators  

    def set_published_city(self,book) : 
        published_city = book.find_all('span', class_="place")
        if len(published_city) == 0: 
            self.published_city = " "
        else: self.published_city = published_city[0].contents[0]

    def set_publisher(self,book) : 
        publisher = book.find_all('span', class_="place")
        if len(publisher) == 0: 
            self.publisher = " "
        else: self.publisher = publisher[0].contents[0] 

    def set_published_country(self,book) : 
        published_country = book.find_all('span', \
                                        class_="sn_country")
        if len(published_country) == 0: 
            self.published_country = " "
        else: self.published_country = published_country[0].contents[0]

    def set_year(self,book) : 
        year = book.find_all('span', class_="sn_year")
        if len(year) == 0: 
            self.year = " "
        else: self.year = year[0].contents[0]   

    def set_pages(self,book) : 
        pages = book.find_all('span', class_="sn_pagination")
        if len(pages) == 0: 
            self.pages = " "
        else: self.pages = pages[0].contents[0] 

    def set_edition(self, book) :
        edition = book.find_all('span', class_="sn_editionstat")
        if len(edition) == 0: 
            self.edition = " "
        else: self.edition = edition[0].contents[0]

    def set_original_title(self,book) : 
        original_title = book.find_all('span', class_="sn_orig_title")
        if len(original_title) == 0: 
            self.original_title = " "
        else: self.original_title = original_title[0].contents[0]   

    def set_original_language(self,book) :
        languages = ''
        original_languages = book.find_all('span', \
                                         class_="sn_orig_lang")

        for language in original_languages:
            languages = languages + language.getText() + ', '

        self.original_languages = languages

    def export(self, country): 
        ''' Function to allow us to easilly pull the text from the 
        contents of the Book object's attributes and write them to the 
        country in which the book was published's CSV file.'''

        file_name = os.path.join(destination_directory + country + ".csv")

        with open(file_name, "a") as by_country_csv:        
            print(self.author.encode('UTF-8') + " & " + \
                  self.quality.encode('UTF-8') + " & " + \
                  self.target_title.encode('UTF-8') + " & " + \
                  self.target_language.encode('UTF-8') + " & " + \
                  self.translators.encode('UTF-8') + " & " + \
                  self.published_city.encode('UTF-8') + " & " + \
                  self.publisher.encode('UTF-8') + " & " + \

                  self.published_country.encode('UTF-8') + " & " + \
                  self.year.encode('UTF-8') + " & " + \
                  self.pages.encode('UTF-8') + " & " + \
                  self.edition.encode('UTF-8') + " & " + \
                  self.original_title.encode('UTF-8') + " & " + \
                  self.original_languages.encode('UTF-8'), file=by_country_csv)

        by_country_csv.close()

    def __init__(self, book, country):
        ''' Initialize the Book object by feeding it the HTML for its 
        row'''
        self.set_author(book)
        self.set_quality(book)
        self.set_target_title(book)
        self.set_target_language(book)

        self.set_translator_name(book)
        self.set_published_city(book)
        self.set_publisher(book)
        self.set_published_country(book)

        self.set_year(book)
        self.set_pages(book)
        self.set_edition(book)
        self.set_original_title(book)

        self.set_original_language(book)


def get_all_pages(country,base_url):
    ''' Create a list of URLs to be crawled by adding the ISO_3166-1_alpha-3
    country code to the URL and then iterating through the results every 10
    pages. Returns a string.'''

    base_page = urllib2.urlopen(base_url+country)
    page = BeautifulSoup(base_page, parse_only=only_restable)

    result_number = page.find_all('td',class_="res1",limit=1)
    if not result_number:
        return 0

    str_result_number = str(result_number[0].getText())
    results_total = int(str_result_number.split('/')[1])

    page.decompose()

    return results_total


def build_list(country_code_list, countries):
    '''  Build the list of all the books, and return a list of Book objects
    in case you want to do something with them in something else, ever.'''
    for country in country_code_list:

        print("Processing %s now..." % countries[country])
        results_total = get_all_pages(country, base_url)

        for url in range(results_total):
            if url % 10 == 0 :
                all_books = []  
                target_page = urllib2.urlopen(base_url + country \
                                             +"&fr="+str(url))
                page = BeautifulSoup(target_page, parse_only=only_restable)
                books = page.find_all('td',class_="res2")
                for book in books:
                    all_books.append(Book (book,country))
                page.decompose()

                for title in all_books:
                    title.export(country)    
    return

if __name__ == "__main__":
    build_list(country_code_list,countries)
    print("Completed.")
4

2 に答える 2

7

いくつかの問題点や考えられる改善点を、順不同でリストアップします。

  1. PEP 8に従ってください。

    現在、 のようなキャメルケースを使用して名前が付けられた変数と関数がたくさんありますsetAuthor。これは Python の従来のスタイルではありません。Python は通常、set_author(などpublished_countryではなくPublishedCountry) という名前を付けます。呼び出しているものの名​​前を変更することもできます。たとえば、BeautifulSoup はfindAll互換性をサポートしていますが、find_all推奨されています。

    命名の他に、PEP 8 は他にもいくつかのことを指定しています。たとえば、次のように書き直します。

    if len(resultNumber) == 0 : return 0
    

    このように:

    if len(result_number) == 0:
        return 0
    

    または空のリストが偽であるという事実を考慮に入れることさえ:

    if not result_number:
        return 0
    
  2. SoupStrainerに aを渡しBeautifulSoupます。

    探している情報は、おそらくドキュメントの一部にしかありません。全体をツリーに解析する必要はありません。への引数としてa を渡します。SoupStrainerparse_onlyBeautifulSoupこれにより、不要な部分が早期に破棄されるため、メモリ使用量が削減されます。

  3. decompose食べ終わったらスープ。

    Pythonは主に参照カウントを使用するため、(そうであるように) すべての循環参照を削除するdecomposeと、ガベージ コレクション、参照カウントの主要なメカニズムが使用され、多くのメモリが解放されます。Python には、循環参照を処理する半伝統的なガベージ コレクターもありますが、参照カウントははるかに高速です。

  4. Book.__init__ディスクに書き込みを行わないでください。

    ほとんどの場合、ディスクに何かを書き込むためにクラスのインスタンスを作成するだけでは期待できません。への呼び出しを削除しますexportexportディスクに置きたい場合は、ユーザーに呼び出してもらいます。

  5. メモリ内に大量のデータを保持するのはやめましょう。

    後でエクスポートするためだけに、このすべてのデータを辞書に蓄積しています。メモリを削減するためにすべきことは、できるだけ早くディスクにダンプすることです。あなたのコメントは、柔軟にするために辞書に入れていることを示しています。しかし、それはすべてをリストに集めなければならないという意味ではありません: ジェネレーターを使用して、アイテムをかき集めながら生成します。次に、ユーザーはリストのように反復できます。

    for book in scrape_books():
        book.export()
    

    …しかし、一度に多くても 1 冊の本しか記憶に残らないという利点があります。

  6. os.path自分でパスを変更するのではなく、関数を使用してください。

    パス名に関しては、現在のコードはかなり脆弱です。から末尾のスラッシュを誤って削除するとdestinationDirectory、意図しないことが起こります。を使用すると、それが発生するのをos.path.join防ぎ、クロスプラットフォームの違いに対処できます。

    >>> os.path.join("/Users/robbie/Test/", "USA")
    '/Users/robbie/Test/USA'
    >>> os.path.join("/Users/robbie/Test", "USA")  # still works!
    '/Users/robbie/Test/USA'
    >>> # or say we were on Windows:
    >>> os.path.join(r"C:\Documents and Settings\robbie\Test", "USA")
    'C:\\Documents and Settings\\robbie\\Test\\USA'
    
  7. と略しattrs={"class":...}ますclass_=...

    BeautifulSoup 4.1.2 では での検索が導入されclass_、冗長な の必要がなくなりましたattrs={"class":...}

変更できることは他にもあると思いますが、最初はかなりの数です。

于 2013-07-21T04:49:33.347 に答える
0

結局、ブックリストは何のために欲しいのですか?「for url in range」ブロック (その内部) の最後で各本をエクスポートし、allbooks dict なしで行う必要があります。本当にリストが必要な場合は、完全な Book オブジェクトを保持するのではなく、必要な情報を正確に定義してください。

于 2013-07-21T04:20:03.337 に答える