scrapy+selenium爬虫实战总结
最近编写了一个爬取震坤行(网址:https://www.zkh.com/)所有分类的产品数据以及产品的同型号产品数据,在编写爬虫的过程中遇到不少问题,故记录在这篇博客中为以后的遇到问题时提供解决思路。
问题总结:
- 同一页数据需要滚轮拖动加载才能完全显示,否则不能完全爬取——>滚轮拖拽
- 横向、纵向拖动滚条——scrollTo(x, y),通过此种方法定位元素需要保证每个网页的对应该元素都在固定位置不会改变,否则网站一变就找不到元素了
-
driver.execute_script("window.scrollTo(100,400)") #拖至网页底部 driver.execute_script("window.scrollTo(0,document.body.scrollHeight)") 或driver.execute_script("window.scrollTo(0,10000)") #拖至网页顶部 driver.execute_script("window.scrollTo(10000,0)")
-
- 跳转到目标元素位置,通过这种方法可以处理不同网页,网页变了也可以
-
#移动元素到指定位置并操作(点击) ActionChains(self.bro).move_to_element(self.bro.find_element(By.CLASS_NAME,"nextbtn")).perform() down_data_click = WebDriverWait(self.bro, 5).until(EC.element_to_be_clickable((By.CLASS_NAME, "nextbtn"))) sleep(2) down_data_click.click() #当元素上有其他元素遮挡或未移动到指定元素时可以使用如下方法实现元素点击,click()可以换成其他操作 next_btn=self.bro.find_element(By.CLASS_NAME,'nextbtn') self.bro.execute_script("arguments[0].click();", next_btn)
-
- 等待元素出现,某些网页由于没有加载完毕,这时定位元素会报错找不到元素,可以通过设置等待时间等待元素出现再进行对元素的操作,如下4为等待时间,超过此时间则报错超时
WebDriverWait(self.bro, 4).until(EC.presence_of_element_located((By.CLASS_NAME,'nextbtn')))
- 等待元素出现,某些网页由于没有加载完毕,这时定位元素会报错找不到元素,可以通过设置等待时间等待元素出现再进行对元素的操作,如下4为等待时间,超过此时间则报错超时
- 网页有很多页时需要点击下一页进行翻页操作——翻页操作
- 翻页操作的逻辑如下:
- 等待页面元素加载完毕(WebDriverWait)
-
WebDriverWait(self.bro, 20).until(EC.presence_of_element_located((By.CLASS_NAME, 'nextbtn')))
-
- 将页面定位到指定元素附近,否则不能对元素进行操作
-
#移动到指定元素 ActionChains(self.bro).move_to_element(self.bro.find_element(By.CLASS_NAME,"nextbtn")).perform() - 点击“下一页”元素
-
#nextbtn=self.bro.find_element(By.CLASS_NAME,'nextbtn') #nextbtn.click() #sleep(2) #等待元素可点击并点击 down_data_click = WebDriverWait(self.bro, 5).until(EC.element_to_be_clickable((By.CLASS_NAME, "nextbtn"))) down_data_click.click() sleep(2)
注意在进行点击动作的时候要先获取页数,设置以页面为循环次数的循环体,并判断是否是最后一页,可以使用try...except来进行判断,到达尾页时就不再点击跳出循环,否则会因为到达尾页“下一页”失效而报错。
3.观察网页的数据是否是动态加载的,观察的方法见上一篇博客,遇到动态加载的博客,这时需要使用selenium进行动态数据抓取,并且重写middlewares.py中的下载中间件,简单下载中间件的编写如下,并且在settings.py中把下载中间件的使用取消注释。并且定位元素时尽量使用driver.find_element(By.xxx,'xxx')而不是response.xxx(),因为如果得到的响应网页中还有动态数据且需要翻页获取数据的话这样得不到其他页的数据因为他也是动态加载的,如图3在商品详情页的同型商品还有翻页,要想获得全部数据最好不要用response.xxx()获取同型商品的链接。
-
-
class ZkhDownloaderMiddleware: def process_request(self, request, spider): return None def process_response(self, request, response, spider): bro = spider.bro bro.get(request.url) sleep(1) page_text = bro.page_source new_response = HtmlResponse(url=request.url, body=page_text, encoding='utf-8', request=request) return new_response def process_exception(self, request, exception, spider): pass
-

4.🏆这次实战遇到最让我头疼也是最后才解决的问题就是在调用回调函数的时候时机不对出现漏掉很多网页的请求的问题,具体的问题描述就是我首先抓取首页的商品链接,每抓到一个链接就yield给回调函数获取详情页信息,同时在详情页获取相关这个商品的同型商品连接,然后yield给第二个回调函数进行字段信息获取,
网页商品页和商品详情页的同型商品


parse获取首页商品连接并调用回调函数1

回调函数1获取同型商品连接并调用提取信息回调函数2

这样看起来貌似没问题而且效率还挺高,但是在运行的时候就出现问题了,首先是每次得到的数据总是某个网页的数据,因此在回调函数1中打印response.url和driver.current_url发现这两个果然不一样,而且该页数据包含翻页动态加载数据,需要用selenium进行爬取,但是每次selenium.curren_url的内容都是一样的,查阅资料发现要在settings.py中设置AUTOTHROTTLE_ENABLED = True,设置之后再打印网址果然一样了,具体原因还不太能理解,但是猜测应该是并发数和发送request请求的时间间隔设置的问题,参考scrapy自动限速扩展,另外可以设置CONCURRENT_REQUESTS的值来控制并发数,默认为16。
第二个遇到的问题是发送请求的时间不对引起的,在parse中我每获得一个url就调用回调函数去处理但是这样就出现了每次需要同时获取网址、调用回调函数,在回调函数中又调用回调函数的情况,会乱掉导致数据抓取不全,url丢失的情况,所以后来采取了定义一个列表,每次获取到一个商品连接就append()到列表中,获取到全部商品的链接后再通过for循环遍历调用回调函数,在回调函数1中也是同样的方法,这样就完美结局问题。逻辑就是要先获得全部的商品的连接,通过for循环遍历urls调用回调函数,在商品详情页获取全部同型商品连接后通过循环遍历同型商品url最后调用回调函数2进行数据的保存(yield item)


本次项目基本上遇到的就是这些问题,其中某些问题处理起来真的让人头疼,开始完全没有头绪,还是要巩固好基础知识,把框架的每个部分、每个设置的作用吃透

浙公网安备 33010602011771号