使用Python和Scrapy进行递归爬行
我正在使用scrapy来爬网一个站点。该网站每页有15个列表,然后有一个“下一步”按钮。我遇到了一个问题,在我完成对管道中所有列表的解析之前,我对下一个链接的请求被调用。以下是我的spider的代码:使用Python和Scrapy进行递归爬行,python,django,scrapy,Python,Django,Scrapy,我正在使用scrapy来爬网一个站点。该网站每页有15个列表,然后有一个“下一步”按钮。我遇到了一个问题,在我完成对管道中所有列表的解析之前,我对下一个链接的请求被调用。以下是我的spider的代码: class MySpider(CrawlSpider): name = 'mysite.com' allowed_domains = ['mysite.com'] start_url = 'http://www.mysite.com/' def start_req
class MySpider(CrawlSpider):
name = 'mysite.com'
allowed_domains = ['mysite.com']
start_url = 'http://www.mysite.com/'
def start_requests(self):
return [Request(self.start_url, callback=self.parse_listings)]
def parse_listings(self, response):
hxs = HtmlXPathSelector(response)
listings = hxs.select('...')
for listing in listings:
il = MySiteLoader(selector=listing)
il.add_xpath('Title', '...')
il.add_xpath('Link', '...')
item = il.load_item()
listing_url = listing.select('...').extract()
if listing_url:
yield Request(urlparse.urljoin(response.url, listing_url[0]),
meta={'item': item},
callback=self.parse_listing_details)
next_page_url = hxs.select('descendant::div[@id="pagination"]/'
'div[@class="next-link"]/a/@href').extract()
if next_page_url:
yield Request(urlparse.urljoin(response.url, next_page_url[0]),
callback=self.parse_listings)
def parse_listing_details(self, response):
hxs = HtmlXPathSelector(response)
item = response.request.meta['item']
details = hxs.select('...')
il = MySiteLoader(selector=details, item=item)
il.add_xpath('Posted_on_Date', '...')
il.add_xpath('Description', '...')
return il.load_item()
这些线路就是问题所在。就像我之前说过的,它们是在蜘蛛完成当前页面的爬行之前执行的。在网站的每个页面上,这只会导致我15个列表中的3个被发送到管道中
if next_page_url:
yield Request(urlparse.urljoin(response.url, next_page_url[0]),
callback=self.parse_listings)
这是我的第一个蜘蛛,可能是我的设计缺陷,有更好的方法吗 有关更新的答案,请参见下面的编辑2部分(2017年10月6日更新) 您使用收益率有什么具体原因吗?Yield将返回一个生成器,当对其调用
.next()
时,该生成器将返回请求对象
将您的yield
语句更改为return
语句,事情应该按预期进行
下面是一个生成器示例:
In [1]: def foo(request):
...: yield 1
...:
...:
In [2]: print foo(None)
<generator object foo at 0x10151c960>
In [3]: foo(None).next()
Out[3]: 1
编辑2:
从2017年5月18日发布的Scrapy v1.4.0开始,现在建议使用响应。遵循而不是直接创建Scrapy。请求对象
从:
有一个新的response.follow方法用于创建请求;现在是
在Scrapy Spider中创建请求的推荐方法。这种方法
使编写正确的spider更容易;response.follow有几个
与直接创建scrapy.Request对象相比的优势:
- 它处理相对URL李>
- 它可以在非UTF8页面上正确使用非ascii URL李>
- 除了绝对和相对URL外,它还支持选择器;对于元素,它还可以提取它们的href值
因此,对于上面的OP,将代码更改为:
next_page_url = hxs.select('descendant::div[@id="pagination"]/'
'div[@class="next-link"]/a/@href').extract()
if next_page_url:
yield Request(urlparse.urljoin(response.url, next_page_url[0]),
callback=self.parse_listings)
致:
你可能想调查两件事
您正在爬网的网站可能正在阻止您定义的用户代理
尝试向您的爬行器添加下载延迟
用刮擦代替蜘蛛
因为您的原始问题需要重复导航连续和重复的内容集,而不是未知大小的内容树,所以请使用mechanize(http://wwwsearch.sourceforge.net/mechanize/)还有美丽的乌苏(http://www.crummy.com/software/BeautifulSoup/)
下面是一个使用mechanize实例化浏览器的示例。此外,使用br.follow_链接(text=“foo”)意味着,与示例中的xpath不同,无论祖先路径中元素的结构如何,链接都将被遵循。也就是说,如果他们更新了HTML,脚本就会中断。更松的联轴器将为您节省一些维护。以下是一个例子:
br = mechanize.Browser()
br.set_handle_equiv(True)
br.set_handle_redirect(True)
br.set_handle_referer(True)
br.set_handle_robots(False)
br.addheaders = [('User-agent', 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:9.0.1)Gecko/20100101 Firefox/9.0.1')]
br.addheaders = [('Accept-Language','en-US')]
br.addheaders = [('Accept-Encoding','gzip, deflate')]
cj = cookielib.LWPCookieJar()
br.set_cookiejar(cj)
br.open("http://amazon.com")
br.follow_link(text="Today's Deals")
print br.response().read()
此外,在“下一个15”href中可能有指示分页的内容,例如&index=15。如果所有页面上的项目总数在第一页上可用,则:
soup = BeautifulSoup(br.response().read())
totalItems = soup.findAll(id="results-count-total")[0].text
startVar = [x for x in range(int(totalItems)) if x % 15 == 0]
然后只需迭代startVar并创建url,将startVar的值添加到url中,br.open()将其删除,然后刮取数据。这样,您就不必通过编程方式“查找”页面上的“下一个”链接并单击它以进入下一页-您已经知道所有有效的URL。将代码驱动的页面操作最小化到只处理所需的数据将加快提取速度。有两种方法可以按顺序执行此操作:
通过定义类下的列表\u url
列表
通过在解析清单()
中定义清单url
李>
唯一的区别是冗长。另外,假设有五个页面要获取列表\u URL
。所以也把page=1
放在类下面
在parse_listings
方法中,只发出一次请求。将所有数据放入需要跟踪的元中。也就是说,使用parse_listings
仅解析“头版”
到达行尾后,返回您的物品。这个过程是循序渐进的
class MySpider(CrawlSpider):
name = 'mysite.com'
allowed_domains = ['mysite.com']
start_url = 'http://www.mysite.com/'
listing_url = []
page = 1
def start_requests(self):
return [Request(self.start_url, meta={'page': page}, callback=self.parse_listings)]
def parse_listings(self, response):
hxs = HtmlXPathSelector(response)
listings = hxs.select('...')
for listing in listings:
il = MySiteLoader(selector=listing)
il.add_xpath('Title', '...')
il.add_xpath('Link', '...')
items = il.load_item()
# populate the listing_url with the scraped URLs
self.listing_url.extend(listing.select('...').extract())
next_page_url = hxs.select('descendant::div[@id="pagination"]/'
'div[@class="next-link"]/a/@href').extract()
# now that the front page is done, move on to the next listing_url.pop(0)
# add the next_page_url to the meta data
return Request(urlparse.urljoin(response.url, self.listing_url.pop(0)),
meta={'page': self.page, 'items': items, 'next_page_url': next_page_url},
callback=self.parse_listing_details)
def parse_listing_details(self, response):
hxs = HtmlXPathSelector(response)
item = response.request.meta['item']
details = hxs.select('...')
il = MySiteLoader(selector=details, item=item)
il.add_xpath('Posted_on_Date', '...')
il.add_xpath('Description', '...')
items = il.load_item()
# check to see if you have any more listing_urls to parse and last page
if self.listing_urls:
return Request(urlparse.urljoin(response.url, self.listing_urls.pop(0)),
meta={'page': self.page, 'items': items, 'next_page_url': response.meta['next_page_url']},
callback=self.parse_listings_details)
elif not self.listing_urls and response.meta['page'] != 5:
# loop back for more URLs to crawl
return Request(urlparse.urljoin(response.url, response.meta['next_page_url']),
meta={'page': self.page + 1, 'items': items},
callback=self.parse_listings)
else:
# reached the end of the pages to crawl, return data
return il.load_item()
您可以根据需要多次生成请求或项目
def parse_类别(自我,响应):
#获取指向其他类别的链接
categories=hxs.select('../@href').extract()
#首先,返回CategoryItem
产量l.装载量_项()
对于类别中的url:
#然后返回解析类别的请求
屈服请求(url、self.parse_类别)
我发现这里-我刚刚在代码中修复了同样的问题。我使用了作为Python2.7一部分的SQLite3数据库来修复它:在第一次传递parse函数时,收集信息的每个项都会将其唯一的行放入数据库表中,并且parse回调的每个实例都会将每个项的数据添加到该项的表和行中。保留一个实例计数器,以便最后一个回调解析例程知道它是最后一个,并从数据库或其他地方写入CSV文件。回调可以是递归的,在meta中被告知它要处理哪个解析模式(当然还有哪个项)。对我来说很有魅力。如果有Python,就有SQLite3。当我第一次发现scrapy在这方面的局限性时,我的帖子如下:
这个例子展示了如何使用不同的技术从网站中删除多个后续页面,因为如果使用return,它只会抓取一个列表并停止。它不会迭代每个列表并为其创建请求。你看,我在包含所有15个列表的页面上获得了一些信息,但随后我必须对该列表的各个页面进行爬网,以获得我需要的其余信息。Yield工作得很好,直到我想添加爬网“下一页”的功能。follow看起来不像是Request()的参数,我得到了一个错误得到了一个意外的关键字参数“follow”
这里同样得到了得到了一个意外的关键字参数“follow”
你是怎么做到的
soup = BeautifulSoup(br.response().read())
totalItems = soup.findAll(id="results-count-total")[0].text
startVar = [x for x in range(int(totalItems)) if x % 15 == 0]
class MySpider(CrawlSpider):
name = 'mysite.com'
allowed_domains = ['mysite.com']
start_url = 'http://www.mysite.com/'
listing_url = []
page = 1
def start_requests(self):
return [Request(self.start_url, meta={'page': page}, callback=self.parse_listings)]
def parse_listings(self, response):
hxs = HtmlXPathSelector(response)
listings = hxs.select('...')
for listing in listings:
il = MySiteLoader(selector=listing)
il.add_xpath('Title', '...')
il.add_xpath('Link', '...')
items = il.load_item()
# populate the listing_url with the scraped URLs
self.listing_url.extend(listing.select('...').extract())
next_page_url = hxs.select('descendant::div[@id="pagination"]/'
'div[@class="next-link"]/a/@href').extract()
# now that the front page is done, move on to the next listing_url.pop(0)
# add the next_page_url to the meta data
return Request(urlparse.urljoin(response.url, self.listing_url.pop(0)),
meta={'page': self.page, 'items': items, 'next_page_url': next_page_url},
callback=self.parse_listing_details)
def parse_listing_details(self, response):
hxs = HtmlXPathSelector(response)
item = response.request.meta['item']
details = hxs.select('...')
il = MySiteLoader(selector=details, item=item)
il.add_xpath('Posted_on_Date', '...')
il.add_xpath('Description', '...')
items = il.load_item()
# check to see if you have any more listing_urls to parse and last page
if self.listing_urls:
return Request(urlparse.urljoin(response.url, self.listing_urls.pop(0)),
meta={'page': self.page, 'items': items, 'next_page_url': response.meta['next_page_url']},
callback=self.parse_listings_details)
elif not self.listing_urls and response.meta['page'] != 5:
# loop back for more URLs to crawl
return Request(urlparse.urljoin(response.url, response.meta['next_page_url']),
meta={'page': self.page + 1, 'items': items},
callback=self.parse_listings)
else:
# reached the end of the pages to crawl, return data
return il.load_item()