我正在尝试回答你的问题。
首先,由于您的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 表示。而且Selector和SelectorList类都提供了一些有用的函数来操作结果:
-
extract,返回序列化文档节点列表(转为 unicode 字符串)
-
extract_first,返回标量,first 的 extract 结果
-
re,返回一个列表,re 的 extract 结果
-
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)
重点说明
输出快照:
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)
谢谢。希望这会有所帮助并玩得开心。