質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.60%

scrapyでクロールする際動作が安定しない

受付中

回答 0

投稿

  • 評価
  • クリップ 0
  • VIEW 710

egg_gogo285

score 12

scrapyを用いて以下のページのaタグのhref属性をとってきてDBに保存することを試みています。
(またどのページからのリンクされているのかの情報も合わせてDBに格納したいです。)

http://news.livedoor.com/article/detail/15637521/
https://gendai.ismedia.jp/articles/-/58626
まずこれらのURLをstart_urls.txtというファイルに定義しておきます。

以下ソースコードです

# スパイダー定義
# クロール起点をテキストファイルから取得(シードから深さ1だけクロールする)
class SpiderSpider(scrapy.Spider):
    name = 'spider'
    allowed_domains = []
    start_urls = ()
    sharp = re.compile('#')
    java = re.compile('javascript')
    mailto = re.compile('mailto')

    href_list = []

    def __init__(self, *args, **kwargs):
        super(SpiderSpider, self).__init__(*args, **kwargs)
        # クロール起点ノードを定義したファイルをオープン

        f = open("start_urls.txt")
        urls = f.readlines()
        f.close()

        # 末尾の改行や空行だけのものを削除し、start_urlsにいれる
        self.start_urls = [url.rstrip("\n") for url in urls if url != "\n"]

    def parse(self, response):

        seed_node = ScrapyProjectItem()
        seed_node['url'] = response.url
        seed_node['type'] = "seed"
        seed_node['color'] = "red"
        seed_node['source'] = 0
        yield seed_node

        self.href_list.__init__()
        self.href_list.extend(response.xpath('//body//a/@href').extract())
        self.href_list = list(set(self.href_list))
        for href in self.href_list:
            if href is not None and\
                    self.sharp.search(href.lower()) is\
                    self.java.search(href.lower()) is\
                    self.mailto.search(href.lower()) is None:
                href = response.urljoin(href)
                node = ScrapyProjectItem()
                node['url'] = href
                node['type'] = "normal"
                node['color'] = "white"
                node['source'] = response.url
                yield node

以下pipeline処理です

class ScrapySpiderPipeline(object):
    conn = None
    dname = None

    def __init__(self, settings):
        self.dname = settings.get("DATABASE_NAME")
        self.start_nodes_filename = settings.get("START_URLS")

    @classmethod
    def from_crawler(cls, crawler, *args, **kwargs):
        return cls(settings=crawler.settings)

    def open_spider(self, spider):
        self.conn = sqlite3.connect(self.dname)
        try:
            self.conn.execute("DROP TABLE IF EXISTS vertices")
            self.conn.execute("DROP TABLE IF EXISTS edges")
            self.conn.execute(
                "CREATE TABLE IF NOT EXISTS vertices (url TEXT UNIQUE , nodetype TEXT, color TEXT, auth, hub)"
            )
            self.conn.execute(
                "CREATE TABLE IF NOT EXISTS edges (source, target, weight)"
            )
            self.conn.execute(
                "CREATE UNIQUE INDEX edgeindex ON edges(source, target)"
            )
        except sqlite3.Error:
            print("sqlite3.Error occurred when CREATE.", sqlite3.Error.args[0])
            self.conn.close()
            exit(-1)

        self.conn.commit()

    def close_spider(self, spider):
        # クロールされる順序によってはシードと判定されない場合もあるのでシード情報を上書きする.
        # これはstart_urlsに同じドメインのサイトを複数入れたときなどを想定しています。
        # クロール起点ノードを定義したファイルをオープン
        filename = "../" + self.start_nodes_filename
        with open(filename) as f:
            urls = f.readlines()
            # 末尾の改行や空行だけのものを削除し、start_urls_listにいれる
            start_urls_list = [url.rstrip("\n") for url in urls if url != "\n"]
            # start_urls_list内のノードに関してDBを更新する.
            for st_url in start_urls_list:
                self.conn.execute("UPDATE vertices set nodetype='seed', color='red' where url=\'" + st_url + "\'")
        self.conn.commit()
        self.conn.close()

    def process_item(self, item, spider):
        try:
            self.conn.execute("INSERT INTO vertices VALUES (?,?,?,?,?)", (item["url"], item["type"], item["color"], 0, 0))
        except sqlite3.IntegrityError:
            pass
        if item["source"] != 0:
            try:
                self.conn.execute("INSERT INTO edges VALUES (?,?,?)", (item["source"], item["url"], 0))
            except sqlite3.IntegrityError:
                pass
        self.conn.commit()
        return item

連続で実行してみると拾ってくるitemの数が100以上増減していて動作が安定していないようです。
ログ出力を確認しても以下のようにitem数が大幅に異なっています。

1度目の実行

{'downloader/request_bytes': 1304,
 'downloader/request_count': 4,
 'downloader/request_method_count/GET': 4,
 'downloader/response_bytes': 39756,
 'downloader/response_count': 4,
 'downloader/response_status_count/200': 4,
 'finish_reason': 'finished',
 'finish_time': datetime.datetime(2018, 11, 30, 19, 43, 16, 416240),
 'item_scraped_count': 355,
 'log_count/DEBUG': 360,
 'log_count/ERROR': 1,
 'log_count/INFO': 8,
 'memusage/max': 50589696,
 'memusage/startup': 50589696,
 'response_received_count': 4,
 'scheduler/dequeued': 2,
 'scheduler/dequeued/memory': 2,
 'scheduler/enqueued': 2,
 'scheduler/enqueued/memory': 2,
 'start_time': datetime.datetime(2018, 11, 30, 19, 43, 9, 964071)}

2度目の実行

2018-12-01 04:43:31 [scrapy.statscollectors] INFO: Dumping Scrapy stats:
{'downloader/request_bytes': 1304,
 'downloader/request_count': 4,
 'downloader/request_method_count/GET': 4,
 'downloader/response_bytes': 39756,
 'downloader/response_count': 4,
 'downloader/response_status_count/200': 4,
 'finish_reason': 'finished',
 'finish_time': datetime.datetime(2018, 11, 30, 19, 43, 31, 666102),
 'item_scraped_count': 107,
 'log_count/DEBUG': 112,
 'log_count/ERROR': 1,
 'log_count/INFO': 8,
 'memusage/max': 50585600,
 'memusage/startup': 50585600,
 'response_received_count': 4,
 'scheduler/dequeued': 2,
 'scheduler/dequeued/memory': 2,
 'scheduler/enqueued': 2,
 'scheduler/enqueued/memory': 2,
 'start_time': datetime.datetime(2018, 11, 30, 19, 43, 26, 519444)}
2018-12-01 04:43:31 [scrapy.core.engine] INFO: Spider closed (finished)


2度目にitemとして得られなかったのは
http://news.livedoor.com/article/detail/15637521/
の<div class="rankingBockBody">タグの中などが多いようなのですが
原因がspiderにあるのかpipelineにあるのかも全くわかりません。。

分野に詳しい方、ご意見を聞かせてほしいです、、

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

まだ回答がついていません

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.60%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る