如何用scrapy抓取每个链接的所有内容?

Posted

技术标签:

【中文标题】如何用scrapy抓取每个链接的所有内容?【英文标题】:How to scrape all the content of each link with scrapy? 【发布时间】:2017-03-21 15:48:52 【问题描述】:

我是scrapy的新手,我想从website中提取每个广告的所有内容。所以我尝试了以下方法:

from scrapy.spiders import Spider
from craigslist_sample.items import CraigslistSampleItem

from scrapy.selector import Selector
class MySpider(Spider):
    name = "craig"
    allowed_domains = ["craigslist.org"]
    start_urls = ["http://sfbay.craigslist.org/search/npo"]

    def parse(self, response):
        links = response.selector.xpath(".//*[@id='sortable-results']//ul//li//p")
        for link in links:
            content = link.xpath(".//*[@id='titletextonly']").extract()
            title = link.xpath("a/@href").extract()
            print(title,content)

项目:

# Define here the models for your scraped items

from scrapy.item import Item, Field

class CraigslistSampleItem(Item):
    title = Field()
    link = Field()

但是,当我运行爬虫时,我什么也没得到:

$ scrapy crawl --nolog craig
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]

因此,我的问题是:如何遍历每个 url,进入每个链接并抓取内容和标题?什么是最好的方法?

【问题讨论】:

您是如何想到 XPath 的? .//*[@id='sortable-results']//ul//li//p 看起来 ok,它应该在页面上为您提供 <p class="result-info">。但是在那些<p class="result-info"> 中,我看不到与.//*[@id='titletextonly'] 匹配的东西。您可以使用 scrapy shell 测试您的 XPath scrapy 用法或 XPath 的示例?我相信docs.scrapy.org/en/latest/intro/… 与您的用例非常相似。 每个网站都是不同的,您所追求的数据就是您的用例。也许您想要一门 XPath 课程,this blog post 可以作为很好的介绍。 我会接受谁有更多赞成票的问题,两者都很棒。 【参考方案1】:

要搭建一个基本的scrapy项目,你可以使用command:

scrapy startproject craig

然后添加蜘蛛和项目:

克雷格/蜘蛛/spider.py

from scrapy import Spider
from craig.items import CraigslistSampleItem
from scrapy.linkextractors.lxmlhtml import LxmlLinkExtractor
from scrapy.selector import Selector
from scrapy import Request
import urlparse, re

class CraigSpider(Spider):
    name = "craig"
    start_url = "https://sfbay.craigslist.org/search/npo"

    def start_requests(self):

        yield Request(self.start_url, callback=self.parse_results_page)


    def parse_results_page(self, response):

        sel = Selector(response)

        # Browse paging.
        page_urls = sel.xpath(""".//span[@class='buttons']/a[@class='button next']/@href""").getall()

        for page_url in page_urls + [response.url]:
            page_url = urlparse.urljoin(self.start_url, page_url)

            # Yield a request for the next page of the list, with callback to this same function: self.parse_results_page().
            yield Request(page_url, callback=self.parse_results_page)

        # Browse items.
        item_urls = sel.xpath(""".//*[@id='sortable-results']//li//a/@href""").getall()

        for item_url in item_urls:
            item_url = urlparse.urljoin(self.start_url, item_url)

            # Yield a request for each item page, with callback self.parse_item().
            yield Request(item_url, callback=self.parse_item)


    def parse_item(self, response):

        sel = Selector(response)

        item = CraigslistSampleItem()

        item['title'] = sel.xpath('//*[@id="titletextonly"]').extract_first()
        item['body'] = sel.xpath('//*[@id="postingbody"]').extract_first()
        item['link'] = response.url

        yield item

克雷格/items.py

# -*- coding: utf-8 -*-

# Define here the models for your scraped items

from scrapy.item import Item, Field

class CraigslistSampleItem(Item):
    title = Field()
    body = Field()
    link = Field()

克雷格/settings.py

# -*- coding: utf-8 -*-

BOT_NAME = 'craig'

SPIDER_MODULES = ['craig.spiders']
NEWSPIDER_MODULE = 'craig.spiders'

ITEM_PIPELINES = 
   'craig.pipelines.CraigPipeline': 300,

克雷格/pipelines.py

from scrapy import signals
from scrapy.xlib.pydispatch import dispatcher
from scrapy.exporters import CsvItemExporter

class CraigPipeline(object):

    def __init__(self):
        dispatcher.connect(self.spider_opened, signals.spider_opened)
        dispatcher.connect(self.spider_closed, signals.spider_closed)
        self.files = 

    def spider_opened(self, spider):
        file = open('%s_ads.csv' % spider.name, 'w+b')
        self.files[spider] = file
        self.exporter = CsvItemExporter(file)
        self.exporter.start_exporting()

    def spider_closed(self, spider):
        self.exporter.finish_exporting()
        file = self.files.pop(spider)
        file.close()

    def process_item(self, item, spider):
        self.exporter.export_item(item)
        return item

你可以通过运行command来运行蜘蛛:

scrapy runspider craig/spiders/spider.py

从项目的根目录。

它应该在项目的根目录中创建一个craig_ads.csv

【讨论】:

感谢伊万的帮助。确实,另一个如何通过管道实现的示例对社区很有帮助,您能否为我们提供一个如何实现的示例? 当然!您需要输出的格式是什么? CSV? JSON? Excel 文件? 假设我需要一个 CSV 我也试过scrapy crawl myspider -o items.csv,但文件是空的……知道为什么会这样吗? 这是因为我们没有在parse_item() 的末尾产生结果(为了示例,我们只是打印了项目)。我添加了yield,还添加了导出CSV 文件的管道。请让我知道这是否有帮助。【参考方案2】:

我正在尝试回答你的问题。

首先,由于您的XPath 查询不正确,您得到了空白结果。通过 XPath ".//*[@id='sortable-results']//ul//li//p",您正确定位了相关的 <p> 节点,尽管我不喜欢您的查询表达式。但是,我不知道您的以下 XPath 表达式 ".//*[@id='titletextonly']""a/@href",他们无法按照您的预期找到链接和标题。也许你的意思是找到标题的文本和标题的超链接。如果是,相信你一定要学Xpath,请以HTML DOM开头。

我确实想指导您如何进行 XPath 查询,因为网上有很多资源。我想提一下 Scrapy XPath 选择器的一些特性:

    Scrapy XPath Selector 是标准 XPath 查询的改进包装器。

在标准 XPath 查询中,它返回您查询的 DOM 节点数组。您可以打开浏览器的开发模式(F12),使用控制台命令$x(x_exp)进行测试。我强烈建议通过这种方式测试您的 XPath 表达式。它将为您提供即时结果并节省大量时间。有时间的话,先熟悉一下浏览器的网页开发工具,这样可以让你快速了解网页结构,找到你要找的入口。

同时,Scrapy response.xpath(x_exp) 返回对应于实际 XPath 查询的 Selector 对象数组,这实际上是一个 SelectorList 对象。这意味着 XPath 结果由SelectorsList 表示。而且SelectorSelectorList类都提供了一些有用的函数来操作结果:

extract,返回序列化文档节点列表(转为 unicode 字符串) extract_first,返回标量,firstextract 结果 re,返回一个列表,reextract 结果 re_first,返回标量,first re 结果。

这些功能使您的编程更加方便。一个例子是您可以直接在SelectorList 对象上调用xpath 函数。如果您之前尝试过lxml,您会发现这非常有用:如果您想在之前的xpath 的结果上调用xpath 函数,结果是lxml,您必须迭代之前的结果。另一个例子是,当您确定该列表中最多有一个元素时,您可以使用extract_first 来获取标量值,而不是使用会导致索引不足的列表索引方法(例如,rlist[0])没有匹配的元素时出现异常。请记住,解析网页时总会出现异常,请注意编程并保持稳健。

    绝对 XPath 与 relative XPath

请记住,如果您嵌套 XPathSelector 并使用以 / 开头的 XPath,则该 XPath 将是文档的绝对路径,而不是您从中调用它的 XPathSelector。

操作node.xpath(x_expr)时,如果x_expr/开头,则为绝对查询,XPath将从root开始搜索;否则如果x_expr. 开头,则为相对查询。标准2.5 Abbreviated Syntax

中也注明了这一点

。选择上下文节点

.//para选择上下文节点的para元素后代

.. 选择上下文节点的父节点

../@lang 选择上下文节点的父节点的lang属性

    如何关注下一页和关注结束。

对于您的应用程序,您可能需要遵循下一页。在这里,下一个页面节点很容易找到——有下一个按钮。但是,您还需要注意停止关注的时间。仔细查看您的 URL 查询参数,以了解您的应用程序的 URL 模式。在这里,要确定何时停止关注下一页,您可以将当前项目范围与项目总数进行比较。

新编辑

我对链接内容的含义有些困惑。现在我知道@student 也想抓取链接以提取广告内容。以下是解决方案。

    发送请求并附加其解析器

您可能会注意到,我使用 Scrapy Request 类来关注下一页。实际上,Request 类的强大功能不止于此——您可以通过设置参数callback 为每个请求附加所需的解析函数。

callback (callable) – 将调用此请求的响应(一旦下载)作为其第一个参数的函数。有关更多信息,请参阅下面的将附加数据传递给回调函数。如果请求没有指定回调,则将使用蜘蛛的 parse() 方法。请注意,如果在处理过程中引发异常,则会调用 errback。

在第 3 步中,我在发送下一页请求时没有设置callback,因为这些请求默认应由parse 函数处理。现在来到指定的广告页面,与之前的广告列表页面不同的页面。因此我们需要定义一个新的页面解析器函数,比如说parse_ad,当我们发送每个AD页面请求时,将这个parse_ad函数附加到请求中。

让我们转到适合我的修改后的示例代码:

items.py

# -*- coding: utf-8 -*-

# Define here the models for your scraped items
#
# See documentation in:
# http://doc.scrapy.org/en/latest/topics/items.html

import scrapy


class ScrapydemoItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    title = scrapy.Field()
    link = scrapy.Field()


class AdItem(scrapy.Item):
    title = scrapy.Field()
    description = scrapy.Field()

蜘蛛

# -*- coding: utf-8 -*-
from scrapy.spiders import Spider
from scrapy.http import Request
from scrapydemo.items import ScrapydemoItem
from scrapydemo.items import AdItem
try:
    from urllib.parse import urljoin
except ImportError:
    from urlparse import urljoin


class MySpider(Spider):
    name = "demo"
    allowed_domains = ["craigslist.org"]
    start_urls = ["http://sfbay.craigslist.org/search/npo"]

    def parse(self, response):
        # locate list of each item
        s_links = response.xpath("//*[@id='sortable-results']/ul/li")
        # locate next page and extract it
        next_page = response.xpath(
            '//a[@title="next page"]/@href').extract_first()
        next_page = urljoin(response.url, next_page)
        to = response.xpath(
            '//span[@class="rangeTo"]/text()').extract_first()
        total = response.xpath(
            '//span[@class="totalcount"]/text()').extract_first()
        # test end of following
        if int(to) < int(total):
            # important, send request of next page
            # default parsing function is 'parse'
            yield Request(next_page)

        for s_link in s_links:
            # locate and extract
            title = s_link.xpath("./p/a/text()").extract_first().strip()
            link = s_link.xpath("./p/a/@href").extract_first()
            link = urljoin(response.url, link)
            if title is None or link is None:
                print('Warning: no title or link found: %s', response.url)
            else:
                yield ScrapydemoItem(title=title, link=link)
                # important, send request of ad page
                # parsing function is 'parse_ad'
                yield Request(link, callback=self.parse_ad)

    def parse_ad(self, response):
        ad_title = response.xpath(
            '//span[@id="titletextonly"]/text()').extract_first().strip()
        ad_description = ''.join(response.xpath(
            '//section[@id="postingbody"]//text()').extract())
        if ad_title is not None and ad_description is not None:
            yield AdItem(title=ad_title, description=ad_description)
        else:
            print('Waring: no title or description found %s', response.url)

重点说明

两个解析函数,parse 用于 AD 列表页面的请求,parse_ad 用于指定 AD 页面的请求。 要提取广告帖子的内容,您需要一些技巧。见How can I get all the plain text from a website with Scrapy

输出快照:

2016-11-10 21:25:14 [scrapy] DEBUG: Scraped from <200 http://sfbay.craigslist.org/eby/npo/5869108363.html>
'description': '\n'
                '        \n'
                '            QR Code Link to This Post\n'
                '            \n'
                '        \n'
                'Agency History:\n' ........
 'title': 'Staff Accountant'
2016-11-10 21:25:14 [scrapy] INFO: Dumping Scrapy stats:
'downloader/request_bytes': 39259,
 'downloader/request_count': 117,
 'downloader/request_method_count/GET': 117,
 'downloader/response_bytes': 711320,
 'downloader/response_count': 117,
 'downloader/response_status_count/200': 117,
 'finish_reason': 'shutdown',
 'finish_time': datetime.datetime(2016, 11, 11, 2, 25, 14, 878628),
 'item_scraped_count': 314,
 'log_count/DEBUG': 432,
 'log_count/INFO': 8,
 'request_depth_max': 2,
 'response_received_count': 117,
 'scheduler/dequeued': 116,
 'scheduler/dequeued/memory': 116,
 'scheduler/enqueued': 203,
 'scheduler/enqueued/memory': 203,
 'start_time': datetime.datetime(2016, 11, 11, 2, 24, 59, 242456)
2016-11-10 21:25:14 [scrapy] INFO: Spider closed (shutdown)

谢谢。希望这会有所帮助并玩得开心。

【讨论】:

感谢您的帮助。我仍然不明白的是如何提取每个链接的内容。 @student 提取链接内容是什么意思。你想去那个链接并抓取一些内容吗? @student 好的。现在我明白了,您想要获取广告页面。这并不难,只需发送您已经提取的 url 的请求并为这些请求添加新的解析。我将修改我的代码。拭目以待。 @student,我更新了答案。检查它是否适合您。 当然。 Items.py 非常简单。我为你添加了。

以上是关于如何用scrapy抓取每个链接的所有内容?的主要内容,如果未能解决你的问题,请参考以下文章

如何用scrapy框架抓取网页?

Scrapy抓取所有站点地图链接

python学习之Scrapy爬虫框架

如何用Fiddler抓取https

如何用python写爬虫 知乎

如何用beautifulsoup提取网页某个部分的所有链接? [复制]