Scrapy+seleninu抓取内容同时下载图片几个问题

使用了Scrapy+Seleninm+Scrapy_redis抓取了详情页的内容和图片,贴出需要完善和出问题的代码,做部分解析和说明。

# -*- coding: utf-8 -*-
import time;

from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import Rule
from scrapy_redis.spiders import RedisCrawlSpider #导入RedisCrawSpider,以便使用Scrapy_redis实现1任务队列持久化(可支持暂停或重启爬虫)和2进行重复过滤
from selenium import webdriver;#导入seleninum的Webdriver,以调用无界流利器,实现动态内容的的获取,本文用的是Chrome,当然可以用非常多的浏览器,可以看看源码,支持很多的
from selenium.webdriver.chrome.options import Options #调用Chorme浏览器的启动参数选项

from scrapyYF.items import ScrapyyfItem


class YaofangSpider(RedisCrawlSpider):
name = 'yaofang'
allowed_domains = ['www.jian.com']
start_urls = ['https://www.jian.com/']
redis_key = 'JK:YP'
# 必须是列表
rules = [
# follow=False(不跟进), 只提取首页符合规则的url,然后爬取这些url页面数据,callback解析
# Follow=True(跟进链接), 在次级url页面中继续寻找符合规则的url,如此循环,直到把全站爬取完毕
# Rule(LinkExtractor(allow=(r'/c/category?cat_id=d*$')),follow=True),#要想抓取更多,可以同时放开此项,从分类页也可以找相应的内容
Rule(LinkExtractor(allow=(r'/product/d*.html$'), unique=True), callback='parse_druginfo', follow=True)
# Rule(LinkExtractor(allow=(r'/product/11929.html'), unique=True), callback='parse_druginfo', follow=False)
# Rule(LinkExtractor(allow=(r'/article/d*$'),unique=True), callback='parse_item', follow=True)
]

def __init__(self, *args, **kwargs):
super(YaofangSpider, self).__init__(*args, **kwargs)#如果不调用父类的这个,会出现AttributeError: 'xxxSpider' object has no attribute '_rules'的错误
chrome_opt = Options(); # 创建参数设置对象.
chrome_opt.add_argument('--headless'); # 无界面化.
chrome_opt.add_argument('--disable-gpu'); # 配合上面的无界面化.
chrome_opt.add_argument('--disable-infobars'); # 配合上面的无界面化.
chrome_opt.add_argument('--window-size=1366,768'); # 设置窗口大小, 窗口大小会有影响.
chrome_opt.add_argument('blink-settings=imagesEnabled=false'); # 禁止加载图片
# self.bro=webdriver.Chrome(executable_path=r'D:Python27Scriptschromedriver.exe')
self.bro = webdriver.Chrome(chrome_options=chrome_opt);

def parse_druginfo(self, response):
item = ScrapyyfItem();
item['from_url'] = response.url;
item['addtime'] = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime());
item['updated'] = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime());
item['class_name'] = response.xpath(
'normalize-space(//div[@class="crumb"]/div/a[2]/text())').extract_first() + '_' + response.xpath(
'normalize-space(//div[@class="crumb"]/div/a[3]/text())').extract_first(); # 分类,比如:中西药品_男科药品 extract_first是提取第一个内容,python3以上已经用get()来代替了getall=extract,get=extract_first
item['goods_id'] = "jianke_" + response.xpath(
'normalize-space(//dl[@class="assort"][1]/dd/text())').extract_first(); # 来源+唯一标识,比如:jianke_B13003000675
item['drug_name'] = response.xpath(
'normalize-space(//dl[@class="assort tongyong"]/dd/a/text())').extract_first(); # 药品名称:六味地黄丸
item['goods_name'] = response.xpath(
'normalize-space(//div[@class="det_title"]//h1/text())').extract_first(); # 商品名称:同仁堂 六味地黄丸(浓缩丸) 120s
item['grant_number'] = response.xpath(
'normalize-space(//dl[@class="assort"][2]/dd/span/text())').extract_first(); # '批准文号'
item['ingredient'] = response.xpath(
u"normalize-space(//*[@id='b_1_1']/table//tr[contains(td,'主要原料')]/td[2]/text())").extract_first(); # 主要成份:熟地黄、酒萸肉、牡丹皮、山药、茯苓、泽泻。xpath提取标题为"主要原料"后的内容
item['indiction'] = response.xpath(
u"normalize-space(//*[@id='b_1_1']/table//tr[contains(td,'主要作用')]/td[2]/text())").extract_first(); # 主要作用:滋阴补肾。用于肾阴亏损,头晕耳鸣,腰膝酸软,骨蒸潮热,盗汗遗精。
item['standard'] = response.xpath(
u'normalize-space(//*[@id="b_1_1"]/table//tr[td="产品规格"]/td[2]/text())').extract_first(); # 产品规格:120丸(浓缩丸)
item['usages'] = response.xpath(
u'normalize-space(//*[@id="b_1_1"]/table//tr[td="用法用量"]/td[2]/text())').extract_first(); # 用法用量:口服。一次8丸,一日3次。
item['manual'] = "".join(response.xpath(u'//div[@id="b_2_2"]/div/child::p').extract()); # 抓取说明书的内容
item['imgsrc'] = response.xpath(u'//div[@id="tb21"]/div//child::img/@src').extract();
item['manufacturer'] = response.xpath(
u'normalize-space(//*[@id="b_1_1"]/table//tr[td="生产企业"]/td[2]/text())').extract_first(); # 生产企业:北京同仁堂科技发展股份有限公司制药厂
yield item;


def __del__(self):
self.bro.quit();

  代码总结:

  •  使用RedisCrawSpider,要导入Scrapy_redis可以实现1任务队列持久化(可支持暂停或重启爬虫)和2进行重复过滤
  •    使用CrawlSpider或RedisCrawSpider,如果重构_init函数时,一定要调用父类函数,不然会报AttributeError: 'xxxSpider' object has no attribute '_rules'的错误
  •   使用selenium调用webdriver以使用无界浏览器时,可以调好多无界浏览器,比如:chrome,firefox,safari等,并能设置相应的启动参数.具体,可以看https://www.cnblogs.com/jessicor/p/12072255.html
  •  extract_first是提取第一个内容,python3以上已经用get()来代替了getall=extract,get=extract_first
  •     xpath提取标题为"主要原料"后的内容//*[@id='b_1_1']/table//tr[contains(td,'主要原料')]/td[2]/text()
class safetyChainMiddleware(object):
def process_request(self, request, spider):
request.headers['User_Agent'] = user_agent.generate_user_agent()#调用user_agent生成随机headers头,防止被封
referer = request.url
if referer:
request.headers['Referer'] = referer#生成头信息Referer,以防止被封

class seleniumMiddleware(object):
def isFindElement(self, spider):
try:
# spider.bro.find_element_by_id('b_2').click();
if WebDriverWait(spider.bro, 3).until(
EC.text_to_be_present_in_element((By.XPATH, "//ul/li[@id='b_2']"), u'说明书')):#每0.05ms请求一次,请求3s钟,判断id=b_2,标题等于说明书的元素是否存在,存在则执行下边的模拟单击动作
spider.bro.find_element_by_xpath(u"//ul/li[@id='b_2' and contains(text(),'说明书')]").click();
pass
except:
# spider.bro.quit()
pass
def process_response(self, request, response, spider):
url = request.url
if string.find(url, '.jpg') != -1:#如果是图片,直接返回response,不能用HtmlResponse,不然图片不能正常返回或下载
return response
else:
spider.bro.get(request.url)

return HtmlResponse(url=spider.bro.current_url, body=page_text, encoding='utf8', request=request)#返回无界浏览器返回的response内容信息.

  • 添加自定义中间件,为了防止被封,一般要添加的是user_agent,proxy,referer和cookie,要注意,一定要在process_requess里处理,另外还要注意这个函数的返回值.不同的返回值,会执行不同操作:

    

当每个request通过下载中间件时,该方法被调用。

process_request() 必须返回其中之一: 返回 None 、返回一个 Response 对象、返回一个 Request 对象或raise IgnoreRequest 。

如果其返回 None ,Scrapy将继续处理该request,执行其他的中间件的相应方法,直到合适的下载器处理函数(download handler)被调用, 该request被执行(其response被下载)。

如果其返回 Response 对象,Scrapy将不会调用 任何 其他的 process_request() 或 process_exception() 方法,或相应地下载函数; 其将返回该response。 已安装的中间件的 process_response() 方法则会在每个response返回时被调用。

如果其返回 Request 对象,Scrapy则停止调用 process_request方法并重新调度返回的request。当新返回的request被执行后, 相应地中间件链将会根据下载的response被调用。

如果其raise一个 IgnoreRequest 异常,则安装的下载中间件的 process_exception() 方法会被调用。如果没有任何一个方法处理该异常, 则request的errback(Request.errback)方法会被调用。如果没有代码处理抛出的异常, 则该异常被忽略且不记录(不同于其他异常那样)。

  • process_response,这个处理response,针对图片,一定要单独返回.不要用htmlResponse.这个是无界浏览器返回的内容.我在这儿是做了判断.不然图片不能下载.纠结了好几天,最后好好看了看文档,Step by step若干遍程序,终于找到了原因.scrapy的执行流程很重要.
    process_response的返回值也需要说明下
      

process_request() 必须返回以下之一: 返回一个 Response 对象、 返回一个 Request 对象或raise一个 IgnoreRequest 异常。

如果其返回一个 Response (可以与传入的response相同,也可以是全新的对象), 该response会被在链中的其他中间件的 process_response() 方法处理。

如果其返回一个 Request 对象,则中间件链停止, 返回的request会被重新调度下载。处理类似于 process_request() 返回request所做的那样。

如果其抛出一个 IgnoreRequest 异常,则调用request的errback(Request.errback)。 如果没有代码处理抛出的异常,则该异常被忽略且不记录(不同于其他异常那样)。

 
原文地址:https://www.cnblogs.com/jessicor/p/12109089.html