提取主页结果并使用 Scrapy 继续下一页
Extract main page results and continue to next pages with Scrapy
我正在努力访问 main search page 的所有结果,然后遍历所有可用页面;
def parse(self,response):
for href in response.xpath('//*[@class="link link--minimal"]/@href'):
yield response.follow(href, self.parse_property)
# # follow pagination links
# # can't get the href
page_nums = response.xpath('//*[@class="listings-pagination-select"]/select/option/@value').re(r'\d+')
page_nums[0]=''
有什么建议吗?
所以这个网站的大部分内容都在通过 javascript 加载的页面上。大多数现代网站在 javascript.
调用的页面上都有一些内容
您可以在 chrome 中亲自查看。检查页面 --> 右侧的三个点 --> 更多工具 --> 设置 --> 调试 --> 禁用 javascript.
您将在屏幕上看到 none 本网站的内容。
处理动态内容
scrapy 文档非常明确地详细说明了如何处理动态内容 here。
有两种方法可以处理页面上动态加载的内容。但首先值得了解 Javascript 是如何做到这一点的,而不需要过多地研究 javascript 命令。它向服务器调用 HTTP 请求,并将响应显示在网站页面上。 Javascript 能够做到这一点,原因是大多数现代网站都希望在不刷新页面的情况下显示新内容。 javascript 使用的大多数 HTTP 请求都是通过 API 端点完成的。如果您能找到这个 API 端点,您也许能够执行正确的 HTTP 请求来获取数据。
你可以做两件事
- Re-engineer 这些 HTTP 请求
- 浏览器Activity自动化
第一种方法效率最高,而且不易更改网站本身。它也很适合第二个很慢,更适合复杂的浏览器activity,但它对网站更改更脆弱,不太适合大数据抓取。
要考虑如何做到这一点,你需要掌握 Chrome Dev Tools,你可以监控浏览器的请求和响应,看看是否有正确的 data/requests 你要求。
为此,检查页面并进入网络。打开此选项卡后,您可以记录浏览器的 activity。刷新页面,看看会发生什么。您会看到所有请求。现在您可以指定要查看的请求类型。您在开发工具中看到的 XHR 选项卡代表 XML HTTP 请求。这是任何与服务器交互的 object 将在此选项卡中看到,即 API 请求。因此,导航到那里您可能会看到数据所在的位置。
正好这个网站有数据
您可以看到请求,并且有一个用于 headers、预览等的选项卡。预览选项卡很适合查看请求为您提供的数据。在 headers 部分,您可以准确地看到 HTTP 请求、发出此请求所需的负载等...
这是您可以在开发工具中看到的请求 headers。请注意,这是一个 POST HTTP 请求。另外,请注意 content-type
是 JSON。这意味着 highly-structured 数据。
这是发出正确的 HTTP 请求所需的有效负载请注意,它指定了很多!您可以更改此内容以获得不同的搜索等...玩这个会很有趣。
复制这个cURL命令,我比较懒,你可以用一个网站把这个请求转换成漂亮的格式。
将此复制到 curl.trillworks.com。这会将请求转换为 python 中的良好格式。它把它变成一个请求包格式。
import requests
headers = {
'authority': 'jf6e1ij07f.execute-api.eu-west-1.amazonaws.com',
'accept': 'application/json, text/plain, */*',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36',
'content-type': 'application/json;charset=UTF-8',
'origin': 'https://www.myproperty.co.za',
'sec-fetch-site': 'cross-site',
'sec-fetch-mode': 'cors',
'sec-fetch-dest': 'empty',
'referer': 'https://www.myproperty.co.za/search?last=0.5y&coords%5Blat%5D=-33.9248685&coords%5Blng%5D=18.4240553&coords%5Bnw%5D%5Blat%5D=-33.47127&coords%5Bnw%5D%5Blng%5D=18.3074488&coords%5Bse%5D%5Blat%5D=-34.3598061&coords%5Bse%5D%5Blng%5D=19.00467&description=Cape%20Town%2C%20South%20Africa&status=For%20Sale',
'accept-language': 'en-US,en;q=0.9',
'dnt': '1',
}
data = '{"clientOfficeId":[],"countryCode":"za","sortField":"distance","sortOrder":"asc","last":"0.5y","statuses":["For Sale","Pending Sale","Under Offer","Final Sale","Auction"],"coords":{"lat":"-33.9248685","lng":"18.4240553","nw":{"lat":"-33.47127","lng":"18.3074488"},"se":{"lat":"-34.3598061","lng":"19.00467"}},"radius":2500,"nearbySuburbs":true,"limit":210,"start":0}'
response = requests.post('https://jf6e1ij07f.execute-api.eu-west-1.amazonaws.com/p/search', data=data)
在这里您可以看到正确发出此请求所需的数据和 headers。我建议您尝试一下并提出请求。某些 API 端点需要 headers、cookie、参数等...在这种情况下,您不需要 curl.trillworks 提供的 headers。您确实需要数据,尽管它指定了。
import requests
data = '{"clientOfficeId":[],"countryCode":"za","sortField":"distance","sortOrder":"asc","last":"0.5y","statuses":["For Sale","Pending Sale","Under Offer","Final Sale","Auction"],"coords":{"lat":"-33.9248685","lng":"18.4240553","nw":{"lat":"-33.47127","lng":"18.3074488"},"se":{"lat":"-34.3598061","lng":"19.00467"}},"radius":2500,"nearbySuburbs":true,"limit":210,"start":0}'
response = requests.post('https://jf6e1ij07f.execute-api.eu-west-1.amazonaws.com/p/search', data=data)
我还会花一些时间在 response.json()
上,它将响应转换为 python 字典。要获得您想要的信息,您可以通过嵌套的键和值。您还没有真正指定您的数据需求,所以我不会再谈这个了。但是你可以很容易地 fiddle 解决这个问题,这样你就可以将你想要的内容输入到 scrapy 中。
有了它,您现在可以发出适当的请求,您得到的数据是 JSON object.
代码示例
data = '{"clientOfficeId":[],"countryCode":"za","sortField":"distance","sortOrder":"asc","last":"0.5y","statuses":["For Sale","Pending Sale","Under Offer","Final Sale","Auction"],"coords":{"lat":"-33.9248685","lng":"18.4240553","nw":{"lat":"-33.47127","lng":"18.3074488"},"se":{"lat":"-34.3598061","lng":"19.00467"}},"radius":2500,"nearbySuburbs":true,"limit":210,"start":0}'
def start_requests(self):
url = https://jf6e1ij07f.execute-api.eu-west-1.amazonaws.com/p/search
yield scrapy.Request(url=url, callback=self.parse, meta={'data',self.data})
def parse(self,response):
response.json()
输出
{'count': 1220,
'listings': [{'id': 'mp1825776',
'staffRooms': False,
'propertyMaintype': 'Residential',
'town': 'CAPE TOWN',
'hasvirtualtour': False,
'description': 'This spacious apartment in the Iconic Art Deco Building of Cape Town is waiting for you to view!\nThe double volume ceilings create a spacious feel inside this one bedroom, two bathroom apartment.\nThe staircase is beautifully designed, leading to the bedroom and en suite bathroom\nNatural lighting streams into the open plan dining, lounge and kitchen area through dramatic floor to ceiling famous windows. \n\n24-hour security.\nFully equipped Gym.\nParking Bay within Mutual Heights.\nAirB&B friendly.\nCentrally located with easy access to MyCiti Bus Stops and walking distance to top rated restaurants and coffee shops.\n',
'propertyid': '1384000',
'mainphoto': 'https://s3.entegral.net/p/n1384000_a935e9e1-6c37-4baa-996e-1715c2d75d9d1.jpg',
'priceUnit': None,
'isPriceReduced': False,
......... Continues
解释
我们在变量data
中定义我们需要传递的数据。在函数 start_requests
中,这会自动传递要由蜘蛛处理的请求。但是我们可以决定执行什么类型的请求,而不是在 start_urls 列表中提供 URL。
我们做了一个 scrapy 请求,但我们传递了 meta 参数。这允许我们通过 HTTP 请求传递数据。它需要一个字典,键称为数据,self.data 是我们创建的 class 变量。
在parse方法中,我们使用了response.json() 请确保你有scrapy v2.2+才能使用这个功能。这会将响应转换为 python 字典。
附加信息
- 在进行抓取之前,请始终检查 javascript 如何与网络交互特。始终禁用 javascript 以查看您正在处理的内容,因为在这种情况下,没有 javascript 就看不到数据。这样您就知道何时需要处理动态内容。
有多种方法可以处理动态内容。如果没有要发出的 HTTP 请求或没有端点 API.
scrapy_splash
在 pre-renders HTML 上加上 javascript,这样我们就可以访问页面上显示的 javascript 调用数据。它比硒更好,因为它速度更快,但确实需要一些设置。您需要能够使用 docker 图像。要获得任何灵活性,您还需要使用 LUA 脚本。该文档实际上非常擅长设置它。你可以做一些浏览器 activity 但它不如 selenium 包灵活。
考虑 scrapy_selenium。
如果您不打算使用 splash,您可以使用 scrapy selenium 选项,它将处理请求并模拟浏览器 activity。然而,它在执行 clicking/drop 向下菜单等浏览器活动时并不那么灵活...
如果您知道您有一堆请求或所有页面都需要浏览器 activity,请使用蜘蛛中间件发出基于 selenium 的请求。有点猎枪的方式来做到这一点,但它是一种选择。
正在将 selenium 包 stand-alone 导入脚本。请记住,您的蜘蛛只是 python 脚本,您可以按原样导入 selenium 包。如果您的数据需求很大,selenium 是最后的选择。
我正在努力访问 main search page 的所有结果,然后遍历所有可用页面;
def parse(self,response):
for href in response.xpath('//*[@class="link link--minimal"]/@href'):
yield response.follow(href, self.parse_property)
# # follow pagination links
# # can't get the href
page_nums = response.xpath('//*[@class="listings-pagination-select"]/select/option/@value').re(r'\d+')
page_nums[0]=''
有什么建议吗?
所以这个网站的大部分内容都在通过 javascript 加载的页面上。大多数现代网站在 javascript.
调用的页面上都有一些内容您可以在 chrome 中亲自查看。检查页面 --> 右侧的三个点 --> 更多工具 --> 设置 --> 调试 --> 禁用 javascript.
您将在屏幕上看到 none 本网站的内容。
处理动态内容
scrapy 文档非常明确地详细说明了如何处理动态内容 here。
有两种方法可以处理页面上动态加载的内容。但首先值得了解 Javascript 是如何做到这一点的,而不需要过多地研究 javascript 命令。它向服务器调用 HTTP 请求,并将响应显示在网站页面上。 Javascript 能够做到这一点,原因是大多数现代网站都希望在不刷新页面的情况下显示新内容。 javascript 使用的大多数 HTTP 请求都是通过 API 端点完成的。如果您能找到这个 API 端点,您也许能够执行正确的 HTTP 请求来获取数据。
你可以做两件事
- Re-engineer 这些 HTTP 请求
- 浏览器Activity自动化
第一种方法效率最高,而且不易更改网站本身。它也很适合第二个很慢,更适合复杂的浏览器activity,但它对网站更改更脆弱,不太适合大数据抓取。
要考虑如何做到这一点,你需要掌握 Chrome Dev Tools,你可以监控浏览器的请求和响应,看看是否有正确的 data/requests 你要求。
为此,检查页面并进入网络。打开此选项卡后,您可以记录浏览器的 activity。刷新页面,看看会发生什么。您会看到所有请求。现在您可以指定要查看的请求类型。您在开发工具中看到的 XHR 选项卡代表 XML HTTP 请求。这是任何与服务器交互的 object 将在此选项卡中看到,即 API 请求。因此,导航到那里您可能会看到数据所在的位置。
正好这个网站有数据
您可以看到请求,并且有一个用于 headers、预览等的选项卡。预览选项卡很适合查看请求为您提供的数据。在 headers 部分,您可以准确地看到 HTTP 请求、发出此请求所需的负载等...
这是您可以在开发工具中看到的请求 headers。请注意,这是一个 POST HTTP 请求。另外,请注意 content-type
是 JSON。这意味着 highly-structured 数据。
这是发出正确的 HTTP 请求所需的有效负载请注意,它指定了很多!您可以更改此内容以获得不同的搜索等...玩这个会很有趣。
复制这个cURL命令,我比较懒,你可以用一个网站把这个请求转换成漂亮的格式。
将此复制到 curl.trillworks.com。这会将请求转换为 python 中的良好格式。它把它变成一个请求包格式。
import requests
headers = {
'authority': 'jf6e1ij07f.execute-api.eu-west-1.amazonaws.com',
'accept': 'application/json, text/plain, */*',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36',
'content-type': 'application/json;charset=UTF-8',
'origin': 'https://www.myproperty.co.za',
'sec-fetch-site': 'cross-site',
'sec-fetch-mode': 'cors',
'sec-fetch-dest': 'empty',
'referer': 'https://www.myproperty.co.za/search?last=0.5y&coords%5Blat%5D=-33.9248685&coords%5Blng%5D=18.4240553&coords%5Bnw%5D%5Blat%5D=-33.47127&coords%5Bnw%5D%5Blng%5D=18.3074488&coords%5Bse%5D%5Blat%5D=-34.3598061&coords%5Bse%5D%5Blng%5D=19.00467&description=Cape%20Town%2C%20South%20Africa&status=For%20Sale',
'accept-language': 'en-US,en;q=0.9',
'dnt': '1',
}
data = '{"clientOfficeId":[],"countryCode":"za","sortField":"distance","sortOrder":"asc","last":"0.5y","statuses":["For Sale","Pending Sale","Under Offer","Final Sale","Auction"],"coords":{"lat":"-33.9248685","lng":"18.4240553","nw":{"lat":"-33.47127","lng":"18.3074488"},"se":{"lat":"-34.3598061","lng":"19.00467"}},"radius":2500,"nearbySuburbs":true,"limit":210,"start":0}'
response = requests.post('https://jf6e1ij07f.execute-api.eu-west-1.amazonaws.com/p/search', data=data)
在这里您可以看到正确发出此请求所需的数据和 headers。我建议您尝试一下并提出请求。某些 API 端点需要 headers、cookie、参数等...在这种情况下,您不需要 curl.trillworks 提供的 headers。您确实需要数据,尽管它指定了。
import requests
data = '{"clientOfficeId":[],"countryCode":"za","sortField":"distance","sortOrder":"asc","last":"0.5y","statuses":["For Sale","Pending Sale","Under Offer","Final Sale","Auction"],"coords":{"lat":"-33.9248685","lng":"18.4240553","nw":{"lat":"-33.47127","lng":"18.3074488"},"se":{"lat":"-34.3598061","lng":"19.00467"}},"radius":2500,"nearbySuburbs":true,"limit":210,"start":0}'
response = requests.post('https://jf6e1ij07f.execute-api.eu-west-1.amazonaws.com/p/search', data=data)
我还会花一些时间在 response.json()
上,它将响应转换为 python 字典。要获得您想要的信息,您可以通过嵌套的键和值。您还没有真正指定您的数据需求,所以我不会再谈这个了。但是你可以很容易地 fiddle 解决这个问题,这样你就可以将你想要的内容输入到 scrapy 中。
有了它,您现在可以发出适当的请求,您得到的数据是 JSON object.
代码示例
data = '{"clientOfficeId":[],"countryCode":"za","sortField":"distance","sortOrder":"asc","last":"0.5y","statuses":["For Sale","Pending Sale","Under Offer","Final Sale","Auction"],"coords":{"lat":"-33.9248685","lng":"18.4240553","nw":{"lat":"-33.47127","lng":"18.3074488"},"se":{"lat":"-34.3598061","lng":"19.00467"}},"radius":2500,"nearbySuburbs":true,"limit":210,"start":0}'
def start_requests(self):
url = https://jf6e1ij07f.execute-api.eu-west-1.amazonaws.com/p/search
yield scrapy.Request(url=url, callback=self.parse, meta={'data',self.data})
def parse(self,response):
response.json()
输出
{'count': 1220,
'listings': [{'id': 'mp1825776',
'staffRooms': False,
'propertyMaintype': 'Residential',
'town': 'CAPE TOWN',
'hasvirtualtour': False,
'description': 'This spacious apartment in the Iconic Art Deco Building of Cape Town is waiting for you to view!\nThe double volume ceilings create a spacious feel inside this one bedroom, two bathroom apartment.\nThe staircase is beautifully designed, leading to the bedroom and en suite bathroom\nNatural lighting streams into the open plan dining, lounge and kitchen area through dramatic floor to ceiling famous windows. \n\n24-hour security.\nFully equipped Gym.\nParking Bay within Mutual Heights.\nAirB&B friendly.\nCentrally located with easy access to MyCiti Bus Stops and walking distance to top rated restaurants and coffee shops.\n',
'propertyid': '1384000',
'mainphoto': 'https://s3.entegral.net/p/n1384000_a935e9e1-6c37-4baa-996e-1715c2d75d9d1.jpg',
'priceUnit': None,
'isPriceReduced': False,
......... Continues
解释
我们在变量data
中定义我们需要传递的数据。在函数 start_requests
中,这会自动传递要由蜘蛛处理的请求。但是我们可以决定执行什么类型的请求,而不是在 start_urls 列表中提供 URL。
我们做了一个 scrapy 请求,但我们传递了 meta 参数。这允许我们通过 HTTP 请求传递数据。它需要一个字典,键称为数据,self.data 是我们创建的 class 变量。
在parse方法中,我们使用了response.json() 请确保你有scrapy v2.2+才能使用这个功能。这会将响应转换为 python 字典。
附加信息
- 在进行抓取之前,请始终检查 javascript 如何与网络交互特。始终禁用 javascript 以查看您正在处理的内容,因为在这种情况下,没有 javascript 就看不到数据。这样您就知道何时需要处理动态内容。
有多种方法可以处理动态内容。如果没有要发出的 HTTP 请求或没有端点 API.
scrapy_splash 在 pre-renders HTML 上加上 javascript,这样我们就可以访问页面上显示的 javascript 调用数据。它比硒更好,因为它速度更快,但确实需要一些设置。您需要能够使用 docker 图像。要获得任何灵活性,您还需要使用 LUA 脚本。该文档实际上非常擅长设置它。你可以做一些浏览器 activity 但它不如 selenium 包灵活。
考虑 scrapy_selenium。 如果您不打算使用 splash,您可以使用 scrapy selenium 选项,它将处理请求并模拟浏览器 activity。然而,它在执行 clicking/drop 向下菜单等浏览器活动时并不那么灵活...
如果您知道您有一堆请求或所有页面都需要浏览器 activity,请使用蜘蛛中间件发出基于 selenium 的请求。有点猎枪的方式来做到这一点,但它是一种选择。
正在将 selenium 包 stand-alone 导入脚本。请记住,您的蜘蛛只是 python 脚本,您可以按原样导入 selenium 包。如果您的数据需求很大,selenium 是最后的选择。