我正在努力回答你的问题。
首先,由于你使用了错误的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表达式。它将为您提供即时结果并节省大量时间。如果您有时间,请熟悉浏览器的Web开发工具,这将让您快速了解网页结构并找到您要查找的入口。
而Scrapy
response.xpath(x_exp)
返回与实际XPath查询对应的
Selector
对象数组,实际上是一个
SelectorList
对象。这意味着XPath结果由
SelectorsList
表示。
Selector
和
SelectorList
类都提供了一些有用的函数来操作结果:
extract
,返回序列化文档节点列表(到Unicode字符串)
extract_first
,返回标量,extract
结果的first
re
,返回列表,extract
结果的re
re_first
,返回标量,re
结果的first
这些函数使你的编程更加方便。一个例子是,你可以直接在
SelectorList
对象上调用
xpath
函数。如果你之前尝试过
lxml
,你会发现这非常有用:如果你想在
lxml
中对前一个
xpath
结果调用
xpath
函数,你必须遍历前一个结果。另一个例子是,当你确定该列表中最多只有一个元素时,你可以使用
extract_first
来获取标量值,而不是使用列表索引方法(例如
rlist[0]
),否则当没有匹配元素时会导致索引超出范围异常。记住,在解析网页时总会有例外情况,要小心并且保持编程的健壮性。
2. 绝对XPath vs.
相对XPath
请记住,如果您嵌套XPathSelectors并使用以/开头的XPath,则该XPath将针对文档而不是相对于调用它的XPathSelector进行绝对查询。
当您执行操作node.xpath(x_expr)时,如果x_expr以/开头,则它是一个绝对查询,XPath将从根开始搜索;否则,如果x_expr以.开头,则它是一个相对查询。这也在标准
2.5缩写语法中注明。
.选择上下文节点
.//para选择上下文节点的para元素后代
..选择上下文节点的父级
../@lang选择上下文节点的父级的lang属性
如何跟随下一页和结束以下。
对于你的应用程序,你可能需要遵循下一页。在这里,下一页节点很容易找到--有下一页按钮。但是,你还需要注意停止跟踪的时间。仔细查看URL查询参数以告诉你的应用程序的URL模式。在这里,为了确定何时停止跟随下一页,可以将当前项目范围与项目的总数进行比较。
新编辑
我有点困惑链接内容的含义。现在我明白了@学生想要爬取链接以提取AD内容。以下是解决方案。
4.发送请求并附加其解析器
正如你可能已经注意到的,我使用Scrapy的请求类来跟随下一页。实际上,
Request类的强大之处不仅限于此--你可以通过设置参数回调为每个请求附加所需的解析函数。
回调函数 (callable) - 这个请求的响应(一旦下载完成)将作为其第一个参数被传递给该函数。有关详细信息,请参见下面的向回调函数传递其他数据。如果请求没有指定回调函数,则会使用爬虫的parse()方法。请注意,如果在处理过程中引发异常,则会调用errback。
在步骤3中,我发送下一页请求时没有设置callback
,因为这些请求应该由默认的parse
函数处理。现在来到了指定的广告页面,这是与前一个广告列表页面不同的页面。因此,我们需要定义一个新的页面解析器函数,比如说parse_ad
,当我们发送每个广告页面请求时,将这个parse_ad
函数附加到请求上。
让我们看看对我有效的修改后的示例代码:
items.py
import scrapy
class ScrapydemoItem(scrapy.Item):
title = scrapy.Field()
link = scrapy.Field()
class AdItem(scrapy.Item):
title = scrapy.Field()
description = scrapy.Field()
蜘蛛
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):
s_links = response.xpath("//*[@id='sortable-results']/ul/li")
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()
if int(to) < int(total):
yield Request(next_page)
for s_link in s_links:
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)
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)
谢谢。希望这对你有所帮助,玩得开心。
.//*[@id='sortable-results']//ul//li//p
看起来还不错,应该能够得到页面上的<p class="result-info">
。但在这些<p class="result-info">
中,我看不到与.//*[@id='titletextonly']
匹配的内容。你可以使用scrapy shell
测试你的 XPaths。 - paul trmbrth