1、爬虫子模块的使用 1.1.通过parse函数返回字典数据 通过(scrapy genspider 爬虫名称 目标网址)命令可以创建我们的爬虫子模块,爬虫子模块的作用主要是负责定义白名单、起始请求的url地址、以及对response对象的数据清洗。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 def parse (self, response: HtmlResponse, **kwargs ): a_list = response.xpath("//div[@class='rank-list']/a" ) for a_temp in a_list: rank_number = a_temp.xpath('./div[@class="badge"]/text()' ).extract_first() img_url = a_temp.xpath('./img/@src' ).extract_first() title = a_temp.xpath('./div[@class="content"]/div[@class="title"]/text()' ).extract_first() desc = a_temp.xpath('./div[@class="content"]/div[@class="desc"]/text()' ).extract_first() play_number = a_temp.xpath('.//div[@class="info-item"][1]/span/text()' ).extract_first() yield { 'type' : 'info' , 'rank_number' : rank_number, 'img_url' : img_url, 'title' : title, 'desc' : desc, 'play_number' : play_number }
注意: yield能够传递的对象只有: BaseItem、Request、dict、None
1.2.通过parse函数返回request对象 在实际爬虫过程中我们可能需要先提取到一个url,再通过url再次发起请求去获取对应的数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 def parse (self, response: HtmlResponse, **kwargs ): a_list = response.xpath("//div[@class='rank-list']/a" ) for a_temp in a_list: rank_number = a_temp.xpath('./div[@class="badge"]/text()' ).extract_first() img_url = a_temp.xpath('./img/@src' ).extract_first() title = a_temp.xpath('./div[@class="content"]/div[@class="title"]/text()' ).extract_first() desc = a_temp.xpath('./div[@class="content"]/div[@class="desc"]/text()' ).extract_first() play_number = a_temp.xpath('.//div[@class="info-item"][1]/span/text()' ).extract_first() yield { 'rank_number' : rank_number, 'img_url' : img_url, 'title' : title, 'desc' : desc, 'play_number' : play_number } """ callback: 指定解析函数 cb_kwargs: 如果解析方法中存在形参,则可以通过cb_kwargs传递,传递参数的类型必须是字典,字典中的key必须与解析方法中的形参名称保持一致 """ yield scrapy.Request(img_url, callback=self.parse_imgage, cb_kwargs={'image_name' : title}) def parse_imgage (self, response, image_name ): yield { 'image_name' : image_name.replace('|' , '' ) + '.png' , 'image_content' : response.body }
在上一个例子中我们通过yield返回了一个request对象,通过callback定义了一个回调函数,这个回调函数主要负责去对该request请求的响应结果进行解析,通过cb_kwargs将回调函数需要的参数值进行传递。注意: cb_kwargs接收的是一个字典,并且字典中的key名称必须与回调函数的形参名称保持一致。
1.3.start_request方法的使用 假设我们需要访问多页的数据或者访问的是一个api接口,如果将所有的请求都放在start_urls列表中就会显得很臃肿,并且可扩展性不高,那么我们可以在爬虫子类中重写start_requests方法来定义我们请求的规则
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Top250Spider (scrapy.Spider): """ 如果使用spider类默认的请求方式,则不会对重复请求进行过滤 会重复请求相同的url """ name = "top250" allowed_domains = ["movie.douban.com" ] start_urls = ["http://movie.douban.com/top250" , "http://movie.douban.com/top250" ] def start_requests (self ): url = 'https://movie.douban.com/top250?start={}&filter=' for page in range (10 ): yield scrapy.Request(url.format (page * 25 ), dont_filter=False )
当我们重写了start_requests方法时,爬虫启动后则不会去遍历start_urls列表构造request请求,而是直接进入start_requests方法中构造我们自己定义的请求规则。scrapy.Request方法中有一个参数dont_filter,这个参数主要的作用是在请求url时不会重复请求相同的url
1.4.发送post请求 1.4.1.发送表单数据 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class JcInfoSpider (scrapy.Spider): name = "jc_info" def start_requests (self ): url = 'http://www.xxx.com.cn/new/disclosure' for page in range (1 , 11 ): data = { "column" : "szse_latest" , "pageNum" : str (page), "pageSize" : "30" , "sortName" : "" , "sortType" : "" , "clusterFlag" : "true" } yield scrapy.FormRequest(url=url, formdata=data)
1.4.2.发送json数据 1 2 3 4 5 6 7 8 9 10 11 12 13 from scrapy.http import JsonRequestclass NetEaseInfoSpider (scrapy.Spider): name = "netease_info" def start_requests (self ): url = 'https://xxx.com/api/hr163/position/queryPage' for page in range (1 , 10 ): payload = { "currentPage" : page, "pageSize" : 10 } yield JsonRequest(url, data=payload)
发送json数据的时候需要导入JsonRequest方法
2、下载中间件的使用 下载中间件位于middlewares.py文件中,当引擎将request对象交给下载器时会先经过下载中间件,下载中间件可以给request添加请求头或者代理等,自定义的下载中间件需要在setting文件中激活。
2.1.下载中间件给request对象添加请求头 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class UserAgentDownloaderMiddleware : USER_AGENTS_LIST = [ "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)" , "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)" , "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)" , "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)" , "Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6" , "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1" , "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0" , "Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5" ] def process_request (self, request, spider ): print ('下载中间件的process_request方法被调用...' ) user_agent = random.choice(self.USER_AGENTS_LIST) request.headers['User-Agent' ] = user_agent """ 如果返回None, 表示当前的request提交下一个权重低的process_request。 如果传递到最后一个process_request,则传递给下载器进行下载。 如果返回的是response对象则不再请求,把response对象直接交给引擎 """
2.2.下载中间件给request对象添加代理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 class ProxyDownloaderMiddleware : def process_request (self, request, spider ): print ('免费代理中间件 - 代理设置...' ) request.meta['proxy' ] = 'http://117.0.0.1:7890' print (request.meta['proxy' ]) def process_response (self, request, response, spider ): print ('免费中间件 - 代理检测...' ) if response.status != 200 : request.dont_filter = True return request return response
3、管道的使用 管道用于接收spider解析好的数据,通过管道可以将解析好的数据进行保存,使用管道前需要将管在setting文件中进行激活 管道中的方法: process_item(self, item, spider): 管道中必须要有的方法,实现对item数据的处理,一般情况下都会return item,如果没有return item,那么会将None传递给权重较低的process item open_spider(self, spider): 在爬虫开启的时候仅执行一次,可以在该方法中链接数据库、打开文件等 close_spider(self, spider): 在爬虫关闭的时候仅执行一次,可以在该方法中关闭数据库连接、关闭文件对象等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 class TxWorkFilePipeline : def open_spider (self, spider ): if spider.name == 'tx_work_info' : self.file_obj = open ('tx_work_info.txt' , 'a' , encoding='utf-8' ) def process_item (self, item, spider ): """ 在当前方法中可以对item进行数据判断,如果不符合数据要求,一般有两种方式来处理: 1. 扔掉 抛出一个异常中断当前管道,阻止item通过return传递给下一个管道 2. 修复 在当前方法中编辑修复代码逻辑并使用return将修复的数据传递给下一个item 注意: 当前方法如果存在return item则将item数据传递给下一个item 如果return不存在则将None传递给下一个item """ if spider.name == 'tx_work_info' : self.file_obj.write(json.dumps(item, ensure_ascii=False , indent=4 ) + '\n' ) print ('数据写入成功:' , item) return item def close_spider (self, spider ): if spider.name == 'tx_work_info' : self.file_obj.close() class TxWorkMongoPipeline : def open_spider (self, spider ): if spider.name == 'tx_work_info' : self.mongo_client = pymongo.MongoClient(host='192.168.198.130' ) self.collection = self.mongo_client['py_spider' ]['tx_work_info' ] def process_item (self, item, spider ): if spider.name == 'tx_work_info' : self.collection.insert_one(item) print ('数据写入mongodb成功:' , item) return item def close_spider (self, spider ): if spider.name == 'tx_work_info' : self.mongo_client.close()
上述例子中自定义了2个管道类,这两个管道类分别将数据存储在不同的地方,需要注意的是如果某一个管道类需要先执行完再去执行下一个管道,则需要在settings文件中将需要先执行的管道类权重低于后执行管道类的权重
4.settings文件的参数详解
是否开启robots.txt验证
请求延迟
1 2 3 4 DEFAULT_REQUEST_HEADERS = { "Accept" : "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" , "Accept-Language" : "en" , }
设置默认的请求头信息
1 2 3 SPIDER_MIDDLEWARES = { "TxWork.middlewares.SeleniumDownloaderMiddleware" : 543 , }
爬虫中间件的配置信息
1 2 3 DOWNLOADER_MIDDLEWARES = { "TxWork.middlewares.SeleniumDownloaderMiddleware" : 543 , }
下载中间件的配置信息,若要使用下载中间件就需要在settings文件中将下载中间件的注释放开
1 2 3 4 ITEM_PIPELINES = { "TxWork.pipelines.TxWorkFilePipeline" : 300 , "TxWork.pipelines.TxWorkMongoPipeline" : 301 }
管道配置信息,若要使用管道则需要在settings文件中手动开启
1 2 3 EXTENSIONS = { "scrapy.extensions.telnet.TelnetConsole" : None , }
拓展类的配置信息,如果自定义了一些工具类想要使用,则需要在settings文件中启用
5、items的使用 items类主要用于进行字段的校验,如果将数据保存进数据库,数据库表字段名称和我们最终解析返回字典数据中的key不匹配时会导致数据插入失败,我们可以通过在items中定义字段名用于校验解析数据字典的key是否准确。 先将期望的数据库表字段在items中进行声明
1 2 3 4 5 6 7 8 9 10 class BookItem (scrapy.Item): """ items文件主要用于字段校验 """ title = scrapy.Field() price = scrapy.Field() author = scrapy.Field() date_data = scrapy.Field() detail = scrapy.Field() producer = scrapy.Field()
在爬虫文件中导入定义的items类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class BookInfoSpider (scrapy.Spider): name = "book_info" start_urls = ["http://xxx.com/" ] def parse (self, response: HtmlResponse, **kwargs ): li_list = response.xpath('//ul[@class="bigimg"]/li' ) for li in li_list: item = BookItem() item['title' ] = li.xpath('./a/@title' ).extract_first() item['price' ] = li.xpath('./p[@class="price"]/span[1]/text()' ).extract_first() item['author' ] = li.xpath('./p[@class="search_book_author"]/span[1]/a[1]/@title' ).extract_first() item['date_data' ] = li.xpath('./p[@class="search_book_author"]/span[last()-1]/text()' ).extract_first() item['detail' ] = li.xpath('./p[@class="detail"]/text()' ).extract_first() if li.xpath( './p[@class="detail"]/text()' ) else '空' item['producer' ] = li.xpath( './p[@class="search_book_author"]/span[last()]/a/text()' ).extract_first() if li.xpath( './p[@class="search_book_author"]/span[last()]/a/text()' ) else '空' yield item
当我们定义字典的key时,没有按照items声明的key进行定义则会抛出异常