1

vBulletin フォーラム サイトをスクレイピングする次の単純なコードに問題があります。

class ForumSpider(CrawlSpider):
    ...

    rules = (
            Rule(SgmlLinkExtractor(restrict_xpaths="//div[@class='threadlink condensed']"),
            callback='parse_threads'),
            )

    def parse_threads(self, response):

        thread = HtmlXPathSelector(response)

        # get the list of posts
        posts = thread.select("//div[@id='posts']//table[contains(@id,'post')]/*")

        # plist = []
        for p in posts:
            table = ThreadItem()

            table['thread_id'] = (p.select("//input[@name='searchthreadid']/@value").extract())[0].strip()

            string_id = p.select("../@id").extract() # returns a list
            p_id = string_id[0].split("post")
            table['post_id'] = p_id[1]

            # plist.append(table)
            # return plist
            yield table

いくつかの xpath ハックは別として、yieldでこれを実行すると、同じ thread_id と post_id に対して複数のヒットが発生し、非常に奇妙な結果が得られます。何かのようなもの:

114763,1314728
114763,1314728
114763,1314728
114763,1314740
114763,1314740
114763,1314740

return (コメント内) を使用して同じロジックに戻すと、すべて正常に動作します。ジェネレーターの基本的な間違いかもしれないと思いますが、わかりません。同じ投稿が何度もヒットするのはなぜですか? コードがreturnを使用して動作するのにyieldを使用しないのはなぜですか?

要旨の完全なコード スニペットはこちら.

4

1 に答える 1

1

Looks like it is an indentation problem. The following should work the same way as using list and return:

def parse_threads(self, response):

    thread = HtmlXPathSelector(response)

    # get the list of posts
    posts = thread.select("//div[@id='posts']//table[contains(@id,'post')]/*")

    for p in posts:
        table = ThreadItem()

        table['thread_id'] = (p.select("//input[@name='searchthreadid']/@value").extract())[0].strip()

        string_id = p.select("../@id").extract() # returns a list
        p_id = string_id[0].split("post")
        table['post_id'] = p_id[1]

        yield table

UPD: I've fixed and improved the code of your parse_threads method, should work now:

def parse_threads(self, response):
    thread = HtmlXPathSelector(response)
    thread_id = thread.select("//input[@name='searchthreadid']/@value").extract()[0].strip()
    post_id = thread.select("//div[@id='posts']//table[contains(@id,'post')]/@id").extract()[0].split("post")[1]

    # get the list of posts
    posts = thread.select("//div[@id='posts']//table[contains(@id,'post')]/tr[2]")
    for p in posts:
        # getting user_name
        user_name = p.select(".//a[@class='bigusername']/text()").extract()[0].strip()

        # skip adverts
        if 'Advertisement' in user_name:
            continue

        table = ThreadItem()
        table['user_name'] = user_name
        table['thread_id'] = thread_id
        table['post_id'] = p.select("../@id").extract()[0].split("post")[1]

        yield table

Hope that helps.

于 2013-08-07T05:20:49.373 に答える