class Ccgp_sichuanSpider(scrapy.Spider): name = 'ccgp_sichuan_spider' # 需要修改成全局唯一的名字 allowed_domains = ['www.ccgp-sichuan.gov.cn'] # 需要修改成爬取网站的域名 def __init__(self, *args, **kwargs): super(Ccgp_sichuanSpider, self).__init__(*args, **kwargs) self.num = 0 self.site = self.allowed_domains[0] self.crawl_mode = CrawlMode.HISTORY # 用来判断数据是否已经入库的mongo表对象 self.mongo_client = None self.mongo_col_obj = None self.crawl_helper = None # 日志相关 logging.basicConfig( level=logging.DEBUG, format='%(asctime)s %(filename)s : %(levelname)s %(message)s') # Define a RotatingFileHandler rf_handler = logging.handlers.RotatingFileHandler( filename='./{}.log'.format(self.name), mode='a', maxBytes=10 * 1024 * 1024, backupCount=20, ) formatter = logging.Formatter( '%(asctime)s %(filename)s:%(lineno)s : %(levelname)s %(message)s' ) rf_handler.setFormatter(formatter) rf_handler.setLevel(logging.INFO) # Create an root instance logging.getLogger().addHandler(rf_handler) def init_crawl_helper(self): try: if 'spider_name' in self.__dict__: spider_name = self.spider_name else: spider_name = self.name _settings = self.settings self.mongo_client = pymongo.MongoClient( _settings.get('MONGDB_URI')) self.crawl_helper = JyCrawlHelper( spider_name=spider_name, mongo_client=self.mongo_client, db_name=_settings.get('MONGDB_DB_NAME'), col_name=_settings.get('MONGDB_COLLECTION')) except Exception as e: logging.exception('Get get_crawl_helper failed') self.crawl_helper = None def start_requests(self): """ 爬虫默认接口,启动方法 :return: """ # 获取爬取时传过来的参数 # start_time: 开始时间 # end_time: 结束时间 # start_page: 开始页 (优先于start_time) # end_page: 结束页 (优先于end_time) # stop_item: 连续遇到[stop_item]个重复条目后,退出本次爬取 # spider_name: 指定的spider_name,如果不指定,使用self.name # command example: # python3 -m scrapy crawl base_spider -a start_time="2019:01:01" -a end_time="2019:01:02" # python3 -m scrapy crawl ccgp_sichuan_spider -a start_time="2019:01:01" -a end_time="2020:02:25" # py -3 -m scrapy crawl base_spider -a start_time="now" -a end_time="now" -a start_page="700" -a end_page="1000" -a stop_item="10000" assert self.start_time is not None assert self.end_time is not None self.crawl_mode = CrawlMode.REAL_TIME if str( self.start_time).lower() == 'now' else CrawlMode.HISTORY if self.crawl_mode == CrawlMode.HISTORY: if (len(self.start_time) != 10 or len(self.end_time) != 10 or self.start_time[4] != ':' or self.end_time[4] != ':'): logging.error( 'Bad date format start_time:[{}] end_time:[{}]. Example: 2019:01:01' .format(self.start_time, self.end_time)) return else: # 取当天日期 _dt = datetime.fromtimestamp(time.time()) self.start_time = _dt.strftime("%Y:%m:%d") self.end_time = self.start_time # 初始化self.crawl_helper self.init_crawl_helper() # 主要配置项 _source_info = { # 页面的key,保证唯一 'page_1': { # 省级采购公告 # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '四川政府采购', # list页面的base地址 'base_url': 'http://www.ccgp-sichuan.gov.cn/CmsNewsController.do?', # list页面的call_back处理函数 'callback': self.parse_list_page_common, # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 2000, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': '//div[@class="info"]/ul/li', # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': './a/@href', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '政府采购', 'tos_code': '02', 'notice_type': '采购公告', 'notice_type_code': '0201', 'source': '四川政府采购网', 'site_name': '四川政府采购网', 'area_code': '51', 'content_code': '1', 'channelCode': 'sjcg1', 'industryName': '', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_2': { # 市县级采购公告 # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '四川政府采购', # list页面的base地址 'base_url': 'http://www.ccgp-sichuan.gov.cn/CmsNewsController.do?', # list页面的call_back处理函数 'callback': self.parse_list_page_common, # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 10000, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': '//div[@class="info"]/ul/li', # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': './a/@href', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '政府采购', 'tos_code': '02', 'notice_type': '采购公告', 'notice_type_code': '0201', 'source': '四川政府采购网', 'site_name': '四川政府采购网', 'area_code': '51', 'content_code': '1', 'channelCode': 'sjcg2', 'industryName': '', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, } logging.info('start crawling...') # 轮询每个类别 for _k, _v in _source_info.items(): # 仅爬取指定类型 if 'type' in self.__dict__ and self.type != _k: continue # 填充爬取的基本信息 self.crawl_helper.init_crawl_info(_k, _v) # 假定每个类别有不超过100000个页面 for _page_num in range(1000000): # 如果有start_page项,忽略之前的页面 if 'start_page' in self.__dict__ and _page_num < int( self.start_page) - 1: continue # 如果有end_page项 if 'end_page' in self.__dict__ and _page_num > int( self.end_page) + 1: break # 轮询公告中的不同list页面 if self.crawl_helper.get_stop_flag(_k): break # 根据获得下一页的函数,得到下一页的URL _ext_param = { 'channelCode': _v['channelCode'], 'start_time': self.start_time, 'end_time': self.end_time, } _request_url = _v['get_next_page_url'](page_index=_page_num, base_url=_v['base_url'], ext_param=_ext_param) # 生成request _request = scrapy.Request(_request_url, callback=_v['callback']) # 使用proxy # _request.meta['proxy'] = JyScrapyUtil.get_http_proxy() # _request.meta['max_retry_times'] = 5 # logging.info('Use proxy[] to generate scrapy request'.format(_request.meta['proxy'])) # 如果需要js渲染,需要使用下面的函数 # _request = SplashRequest(_request_url, callback=_v['callback'], args={'wait': 2}) # 填充必要的参数 _request.meta['param'] = _v _request.meta['crawl_key'] = _k _request.meta['page_index'] = _page_num + 1 yield _request # 单个类别的爬取结束 self.crawl_helper.stop_crawl_info(_k) logging.info('stop crawling...') def closed(self, reason): if self.mongo_client: self.mongo_client.close() self.crawl_helper.store_crawl_info_2_db(key=None, status='stopped', comment=reason) logging.info('Spider[{}] closed, reason:[{}]'.format( self.name, reason)) @staticmethod def get_normal_next_page_url(page_index, base_url, ext_param): _param = { 'channelCode': ext_param['channelCode'], 'method': "recommendBulletinList", 'rp': "25", 'title': "", 'moreType': "provincebuyBulletinMore", 'page': page_index, } return '{}{}'.format(base_url, urlencode(_param, quote_via=quote_plus)) def parse_list_page_common(self, response): """ 通用版list页面解析 必要条件: :param response: :return: """ assert 'crawl_key' in response.meta assert 'page_index' in response.meta assert 'param' in response.meta assert 'xpath_of_list' in response.meta['param'] assert 'xpath_of_detail_url' in response.meta['param'] assert 'item_parse_class' in response.meta['param'] list_page_content_md5 = hashlib.md5(response.body).hexdigest() logging.info( 'Get page list url, page:[{}], url:[{}], status:[{}], body md5:[{}]' .format(response.meta['page_index'], response.url, response.status, list_page_content_md5)) logging.info('Crawl info: {}'.format(self.crawl_helper.crawl_info)) crawl_key = response.meta['crawl_key'] # 更新状态表记录 self.crawl_helper.store_crawl_info_2_db(crawl_key, 'active') if not self.crawl_helper.should_continue_page_parse( response, crawl_key, list_page_content_md5): return _item_idx = 0 for selector in response.xpath( response.meta['param']['xpath_of_list']): _detail_url = '' try: _item_idx += 1 _detail_url = selector.xpath( response.meta['param'] ['xpath_of_detail_url']).extract_first().replace(" ", "") _unq_id = JyScrapyUtil.get_unique_id(_detail_url) logging.info('Parse item, [{}]-[{}/{}]'.format( crawl_key, _item_idx, response.meta['page_index'])) # 检查记录是否已在库中,并做相应的跳出动作 loop_break, item_break = self.crawl_helper.should_continue_item_parse( crawl_key, _unq_id) if loop_break: return if item_break: continue # 生成并返回爬取item item_parser = response.meta['param']['item_parse_class']( selector) item = item_parser.get_common_raw_item( _id=_unq_id, detail_url=_detail_url, site=self.site, ext_param=response.meta['param']) # 随机休眠 time.sleep(random.randint(50, 100) / 1000.0) # 更新数据库中爬取数量 self.crawl_helper.increase_total_item_num(crawl_key) logging.info('item is: {}'.format(item)) yield item except Exception as e: logging.exception('Handle [{}] failed'.format(_detail_url))
class SichuanSpider(scrapy.Spider): name = 'sichuan_spider' # 需要修改成全局唯一的名字 allowed_domains = ['ggzyjy.sc.gov.cn'] # 需要修改成爬取网站的域名 def __init__(self, *args, **kwargs): super(SichuanSpider, self).__init__(*args, **kwargs) self.num = 0 self.site = self.allowed_domains[0] self.crawl_mode = CrawlMode.HISTORY # 用来判断数据是否已经入库的mongo表对象 self.mongo_client = None self.mongo_col_obj = None self.crawl_helper = None # 日志相关 logging.basicConfig( level=logging.DEBUG, format='%(asctime)s %(filename)s : %(levelname)s %(message)s') # Define a RotatingFileHandler rf_handler = logging.handlers.RotatingFileHandler( filename='./{}.log'.format(self.name), mode='a', maxBytes=10 * 1024 * 1024, backupCount=20, ) formatter = logging.Formatter( '%(asctime)s %(filename)s:%(lineno)s : %(levelname)s %(message)s' ) rf_handler.setFormatter(formatter) rf_handler.setLevel(logging.INFO) # Create an root instance logging.getLogger().addHandler(rf_handler) def init_crawl_helper(self): try: if 'spider_name' in self.__dict__: spider_name = self.spider_name else: spider_name = self.name _settings = self.settings self.mongo_client = pymongo.MongoClient( _settings.get('MONGDB_URI')) self.crawl_helper = JyCrawlHelper( spider_name=spider_name, mongo_client=self.mongo_client, db_name=_settings.get('MONGDB_DB_NAME'), col_name=_settings.get('MONGDB_COLLECTION')) except Exception as e: logging.exception('Get get_crawl_helper failed') self.crawl_helper = None def start_requests(self): """ 爬虫默认接口,启动方法 :return: """ # 获取爬取时传过来的参数 # start_time: 开始时间 # end_time: 结束时间 # start_page: 开始页 (优先于start_time) # end_page: 结束页 (优先于end_time) # stop_item: 连续遇到[stop_item]个重复条目后,退出本次爬取 # spider_name: 指定的spider_name,如果不指定,使用self.name # command example: # python3 -m scrapy crawl sichuan_spider -a start_time="2019:01:01" -a end_time="2019:01:02" # py -3 -m scrapy crawl base_spider -a start_time="now" -a end_time="now" # py -3 -m scrapy crawl base_spider -a start_time="now" -a end_time="now" -a start_page="700" -a end_page="1000" -a stop_item="10000" assert self.start_time is not None assert self.end_time is not None self.crawl_mode = CrawlMode.REAL_TIME if str( self.start_time).lower() == 'now' else CrawlMode.HISTORY if self.crawl_mode == CrawlMode.HISTORY: if (len(self.start_time) != 10 or len(self.end_time) != 10 or self.start_time[4] != ':' or self.end_time[4] != ':'): logging.error( 'Bad date format start_time:[{}] end_time:[{}]. Example: 2019:01:01' .format(self.start_time, self.end_time)) return else: # 取当天日期 _dt = datetime.fromtimestamp(time.time()) self.start_time = _dt.strftime("%Y:%m:%d") self.end_time = self.start_time # 初始化self.crawl_helper self.init_crawl_helper() # 主要配置项 _source_info = { # 页面的key,保证唯一 'page_1': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '四川省全国公共资源交易平台', # list页面的base地址 'base_url': 'http://ggzyjy.sc.gov.cn/inteligentsearch/rest/inteligentSearch/getFullTextData', # list页面的call_back处理函数 'callback': self.parse_list_page_common, 'method': "post", 'requests_type': "dict", # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 700, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': ['result', 'records'], # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': 'linkurl', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '工程建设', 'tos_code': '01', 'source': '四川省全国公共资源交易平台', 'site_name': '四川省全国公共资源交易平台', 'notice_type': "招标公告", 'notice_type_code': "0201", 'area_code': '51', 'content_code': '1', 'industryName': '', 'equal': '002001001', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_2': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '四川省全国公共资源交易平台', # list页面的base地址 'base_url': 'http://ggzyjy.sc.gov.cn/inteligentsearch/rest/inteligentSearch/getFullTextData', # list页面的call_back处理函数 'callback': self.parse_list_page_common, 'method': "post", 'requests_type': "dict", # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 700, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': ['result', 'records'], # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': 'linkurl', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '工程建设', 'tos_code': '01', 'source': '四川省全国公共资源交易平台', 'site_name': '四川省全国公共资源交易平台', 'notice_type': "资格预审补遗/澄清", 'notice_type_code': "0105", 'area_code': '51', 'content_code': '1', 'industryName': '', 'equal': '002001002', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_3': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '四川省全国公共资源交易平台', # list页面的base地址 'base_url': 'http://ggzyjy.sc.gov.cn/inteligentsearch/rest/inteligentSearch/getFullTextData', # list页面的call_back处理函数 'callback': self.parse_list_page_common, 'method': "post", 'requests_type': "dict", # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 700, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': ['result', 'records'], # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': 'linkurl', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '工程建设', 'tos_code': '01', 'source': '四川省全国公共资源交易平台', 'site_name': '四川省全国公共资源交易平台', 'notice_type': "招标文件补遗/澄清", 'notice_type_code': "0105", 'area_code': '51', 'content_code': '1', 'industryName': '', 'equal': '002001003', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_4': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '四川省全国公共资源交易平台', # list页面的base地址 'base_url': 'http://ggzyjy.sc.gov.cn/inteligentsearch/rest/inteligentSearch/getFullTextData', # list页面的call_back处理函数 'callback': self.parse_list_page_common, 'method': "post", 'requests_type': "dict", # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 700, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': ['result', 'records'], # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': 'linkurl', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '工程建设', 'tos_code': '01', 'source': '四川省全国公共资源交易平台', 'site_name': '四川省全国公共资源交易平台', 'notice_type': "流标或终止公告", 'notice_type_code': "0104", 'area_code': '51', 'content_code': '1', 'industryName': '', 'equal': '002001004', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_5': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '四川省全国公共资源交易平台', # list页面的base地址 'base_url': 'http://ggzyjy.sc.gov.cn/inteligentsearch/rest/inteligentSearch/getFullTextData', # list页面的call_back处理函数 'callback': self.parse_list_page_common, 'method': "post", 'requests_type': "dict", # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 700, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': ['result', 'records'], # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': 'linkurl', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '工程建设', 'tos_code': '01', 'source': '四川省全国公共资源交易平台', 'site_name': '四川省全国公共资源交易平台', 'notice_type': "开标记录", 'notice_type_code': "0102", 'area_code': '51', 'content_code': '1', 'industryName': '', 'equal': '002001005', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_6': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '四川省全国公共资源交易平台', # list页面的base地址 'base_url': 'http://ggzyjy.sc.gov.cn/inteligentsearch/rest/inteligentSearch/getFullTextData', # list页面的call_back处理函数 'callback': self.parse_list_page_common, 'method': "post", 'requests_type': "dict", # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 700, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': ['result', 'records'], # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': 'linkurl', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '工程建设', 'tos_code': '01', 'source': '四川省全国公共资源交易平台', 'site_name': '四川省全国公共资源交易平台', 'notice_type': "评标结果公示", 'notice_type_code': "0104", 'area_code': '51', 'content_code': '1', 'industryName': '', 'equal': '002001006', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_7': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '四川省全国公共资源交易平台', # list页面的base地址 'base_url': 'http://ggzyjy.sc.gov.cn/inteligentsearch/rest/inteligentSearch/getFullTextData', # list页面的call_back处理函数 'callback': self.parse_list_page_common, 'method': "post", 'requests_type': "dict", # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 700, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': ['result', 'records'], # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': 'linkurl', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '工程建设', 'tos_code': '01', 'source': '四川省全国公共资源交易平台', 'site_name': '四川省全国公共资源交易平台', 'notice_type': "签约履行", 'notice_type_code': "0104", 'area_code': '51', 'content_code': '1', 'industryName': '', 'equal': '002001007', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_8': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '四川省全国公共资源交易平台', # list页面的base地址 'base_url': 'http://ggzyjy.sc.gov.cn/inteligentsearch/rest/inteligentSearch/getFullTextData', # list页面的call_back处理函数 'callback': self.parse_list_page_common, 'method': "post", 'requests_type': "dict", # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 700, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': ['result', 'records'], # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': 'linkurl', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '政府采购', 'tos_code': '02', 'source': '四川省全国公共资源交易平台', 'site_name': '四川省全国公共资源交易平台', 'notice_type': "采购公告", 'notice_type_code': "0201", 'area_code': '51', 'content_code': '1', 'industryName': '', 'equal': '002002001', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_9': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '四川省全国公共资源交易平台', # list页面的base地址 'base_url': 'http://ggzyjy.sc.gov.cn/inteligentsearch/rest/inteligentSearch/getFullTextData', # list页面的call_back处理函数 'callback': self.parse_list_page_common, 'method': "post", 'requests_type': "dict", # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 700, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': ['result', 'records'], # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': 'linkurl', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '政府采购', 'tos_code': '02', 'source': '四川省全国公共资源交易平台', 'site_name': '四川省全国公共资源交易平台', 'notice_type': "更正公告", 'notice_type_code': "0204", 'area_code': '51', 'content_code': '1', 'industryName': '', 'equal': '002002002', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_10': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '四川省全国公共资源交易平台', # list页面的base地址 'base_url': 'http://ggzyjy.sc.gov.cn/inteligentsearch/rest/inteligentSearch/getFullTextData', # list页面的call_back处理函数 'callback': self.parse_list_page_common, 'method': "post", 'requests_type': "dict", # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 700, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': ['result', 'records'], # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': 'linkurl', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '政府采购', 'tos_code': '02', 'source': '四川省全国公共资源交易平台', 'site_name': '四川省全国公共资源交易平台', 'notice_type': "中标公告", 'notice_type_code': "0202", 'area_code': '51', 'content_code': '1', 'industryName': '', 'equal': '002002003', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_11': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '四川省全国公共资源交易平台', # list页面的base地址 'base_url': 'http://ggzyjy.sc.gov.cn/inteligentsearch/rest/inteligentSearch/getFullTextData', # list页面的call_back处理函数 'callback': self.parse_list_page_common, 'method': "post", 'requests_type': "dict", # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 700, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': ['result', 'records'], # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': 'linkurl', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '政府采购', 'tos_code': '02', 'source': '四川省全国公共资源交易平台', 'site_name': '四川省全国公共资源交易平台', 'notice_type': "中标公告", 'notice_type_code': "0202", 'area_code': '51', 'content_code': '1', 'industryName': '', 'equal': '002002003', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_12': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '四川省全国公共资源交易平台', # list页面的base地址 'base_url': 'http://ggzyjy.sc.gov.cn/inteligentsearch/rest/inteligentSearch/getFullTextData', # list页面的call_back处理函数 'callback': self.parse_list_page_common, 'method': "post", 'requests_type': "dict", # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 700, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': ['result', 'records'], # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': 'linkurl', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '政府采购', 'tos_code': '02', 'source': '四川省全国公共资源交易平台', 'site_name': '四川省全国公共资源交易平台', 'notice_type': "履行签约", 'notice_type_code': "0202", 'area_code': '51', 'content_code': '1', 'industryName': '', 'equal': '002002004', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_13': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '四川省全国公共资源交易平台', # list页面的base地址 'base_url': 'http://ggzyjy.sc.gov.cn/inteligentsearch/rest/inteligentSearch/getFullTextData', # list页面的call_back处理函数 'callback': self.parse_list_page_common, 'method': "post", 'requests_type': "dict", # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 700, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': ['result', 'records'], # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': 'linkurl', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '政府采购', 'tos_code': '02', 'source': '四川省全国公共资源交易平台', 'site_name': '四川省全国公共资源交易平台', 'notice_type': "终止公告", 'notice_type_code': "0204", 'area_code': '51', 'content_code': '1', 'industryName': '', 'equal': '002002005', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_14': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '四川省全国公共资源交易平台', # list页面的base地址 'base_url': 'http://ggzyjy.sc.gov.cn/inteligentsearch/rest/inteligentSearch/getFullTextData', # list页面的call_back处理函数 'callback': self.parse_list_page_common, 'method': "post", 'requests_type': "dict", # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 700, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': ['result', 'records'], # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': 'linkurl', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '国有产权', 'tos_code': '05', 'source': '四川省全国公共资源交易平台', 'site_name': '四川省全国公共资源交易平台', 'notice_type': "交易公告", 'notice_type_code': "0501", 'area_code': '51', 'content_code': '1', 'industryName': '', 'equal': '002003001', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_15': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '四川省全国公共资源交易平台', # list页面的base地址 'base_url': 'http://ggzyjy.sc.gov.cn/inteligentsearch/rest/inteligentSearch/getFullTextData', # list页面的call_back处理函数 'callback': self.parse_list_page_common, 'method': "post", 'requests_type': "dict", # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 700, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': ['result', 'records'], # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': 'linkurl', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '国有产权', 'tos_code': '05', 'source': '四川省全国公共资源交易平台', 'site_name': '四川省全国公共资源交易平台', 'notice_type': "交易结果", 'notice_type_code': "0502", 'area_code': '51', 'content_code': '1', 'industryName': '', 'equal': '002003002', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_16': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '四川省全国公共资源交易平台', # list页面的base地址 'base_url': 'http://ggzyjy.sc.gov.cn/inteligentsearch/rest/inteligentSearch/getFullTextData', # list页面的call_back处理函数 'callback': self.parse_list_page_common, 'method': "post", 'requests_type': "dict", # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 700, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': ['result', 'records'], # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': 'linkurl', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '土地使用权', 'tos_code': '03', 'source': '四川省全国公共资源交易平台', 'site_name': '四川省全国公共资源交易平台', 'notice_type': "出让公示", 'notice_type_code': "0301", 'area_code': '51', 'content_code': '1', 'industryName': '', 'equal': '002004001', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_17': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '四川省全国公共资源交易平台', # list页面的base地址 'base_url': 'http://ggzyjy.sc.gov.cn/inteligentsearch/rest/inteligentSearch/getFullTextData', # list页面的call_back处理函数 'callback': self.parse_list_page_common, 'method': "post", 'requests_type': "dict", # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 700, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': ['result', 'records'], # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': 'linkurl', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '土地使用权', 'tos_code': '03', 'source': '四川省全国公共资源交易平台', 'site_name': '四川省全国公共资源交易平台', 'notice_type': "成交宗地", 'notice_type_code': "0302", 'area_code': '51', 'content_code': '1', 'industryName': '', 'equal': '002004003', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, } logging.info('start crawling...') # 轮询每个类别 for _k, _v in _source_info.items(): # 填充爬取的基本信息 self.crawl_helper.init_crawl_info(_k, _v) # 假定每个类别有不超过100000个页面 for _page_num in range(100000): # 轮询公告中的不同list页面 if self.crawl_helper.get_stop_flag(_k): break # 根据获得下一页的函数,得到下一页的URL _request_url = _v['get_next_page_url'](page_index=_page_num, base_url=_v['base_url']) # 生成request if _v["method"] == "post": _type_info_item = _v['equal'] payload = '{"token":"","pn":' + str( _page_num ) + ',"rn":12,"sdt":"","edt":"","wd":"","inc_wd":"","exc_wd":"","fields":"title","cnum":"","sort":"{\\"webdate\\":0}","ssort":"title","cl":500,"terminal":"","condition":[{"fieldName":"categorynum","equal":' + '"' + _type_info_item + '"' + ',"notEqual":null,"equalList":null,"notEqualList":null,"isLike":true,"likeType":2}],"time":[{"fieldName":"webdate","startTime": "1990-01-01 00:00:00","endTime":"2099-12-29 23:59:59"}],"highlights":"","statistics":null,"unionCondition":null,"accuracy":"","noParticiple":"0","searchRange":null,"isBusiness":"1"}' _request = scrapy.FormRequest(url=_request_url, body=payload, callback=_v['callback']) else: _request = scrapy.Request(_request_url, callback=_v['callback']) # 如果需要js渲染,需要使用下面的函数 # _request = SplashRequest(_request_url, callback=_v['callback'], args={'wait': 2}) # 填充必要的参数 _request.meta['param'] = _v _request.meta['crawl_key'] = _k _request.meta['page_index'] = _page_num + 1 yield _request # 单个类别的爬取结束 self.crawl_helper.stop_crawl_info(_k) logging.info('stop crawling...') def closed(self, reason): if self.mongo_client: self.mongo_client.close() self.crawl_helper.store_crawl_info_2_db(key=None, status='stopped', comment=reason) logging.info('Spider[{}] closed, reason:[{}]'.format( self.name, reason)) @staticmethod def get_normal_next_page_url(page_index, base_url): return '{}'.format(base_url) def parse_list_page_common(self, response): """ 通用版list页面解析 必要条件: :param response: :return: """ assert 'crawl_key' in response.meta assert 'page_index' in response.meta assert 'param' in response.meta assert 'xpath_of_list' in response.meta['param'] assert 'xpath_of_detail_url' in response.meta['param'] assert 'item_parse_class' in response.meta['param'] list_page_content_md5 = hashlib.md5(response.body).hexdigest() logging.info( 'Get page list url, page:[{}], url:[{}], status:[{}], body md5:[{}]' .format(response.meta['page_index'], response.url, response.status, list_page_content_md5)) logging.info('Crawl info: {}'.format(self.crawl_helper.crawl_info)) crawl_key = response.meta['crawl_key'] # 更新状态表记录 # self.crawl_helper.store_crawl_info_2_db(crawl_key, 'active') if not self.crawl_helper.should_continue_page_parse( response, crawl_key, list_page_content_md5): return _item_idx = 0 if response.meta['param']['requests_type'] == "dict": _request = response.text.encode('utf-8') _response_data = json.loads(response.text) for _dictn_num in response.meta['param']["xpath_of_list"]: _response_data = _response_data[_dictn_num] for selector in _response_data: _detail_url = '' try: _item_idx += 1 # http://ggzyjy.sc.gov.cn/jyxx/002004/002004003/20200220/fa95a2be-7763-44b9-b94e-33dae1f82aea.html # /jyxx/002004/002004003/20200220/0b9c5576-6c08-4593-9a2c-44c6ec8e3ff7.html _detail_url = 'http://ggzyjy.sc.gov.cn/' + str(selector[ response.meta['param']["xpath_of_detail_url"]]) _unq_id = JyScrapyUtil.get_unique_id(_detail_url) logging.info('Parse item, [{}]-[{}/{}]'.format( crawl_key, _item_idx, response.meta['page_index'])) # 检查记录是否已在库中,并做相应的跳出动作 # loop_break, item_break = self.crawl_helper.should_continue_item_parse(crawl_key, _unq_id) # if loop_break: # return # if item_break: # continue # 生成并返回爬取item item_parser = response.meta['param']['item_parse_class']( selector) item = item_parser.get_common_raw_item( _id=_unq_id, detail_url=_detail_url, site=self.site, ext_param=response.meta['param']) # 随机休眠 time.sleep(random.randint(50, 100) / 1000.0) # 更新数据库中爬取数量 # self.crawl_helper.increase_total_item_num(crawl_key) logging.info('item is: {}'.format(item)) # yield item except Exception as e: logging.exception('Handle [{}] failed'.format(_detail_url)) else: for _selector_num, selector in enumerate( response.xpath(response.meta['param']['xpath_of_list'])): _detail_url = '' try: _item_idx += 1 _url_id = selector.xpath( response.meta['param'] ['xpath_of_detail_url']).extract_first() _url_id = _url_id.split("'")[1].replace('\\r\\n', '') _detail_url = 'http://ec.ccccltd.cn/PMS/biddetail.shtml?id=' + str( _url_id) _unq_id = JyScrapyUtil.get_unique_id(_detail_url) logging.info('Parse item, [{}]-[{}/{}]'.format( crawl_key, _item_idx, response.meta['page_index'])) # 检查记录是否已在库中,并做相应的跳出动作 # loop_break, item_break = self.crawl_helper.should_continue_item_parse(crawl_key, _unq_id) # if loop_break: # return # if item_break: # continue # 生成并返回爬取item item_parser = response.meta['param']['item_parse_class']( selector) item = item_parser.get_common_raw_item( _id=_unq_id, detail_url=_detail_url, site=self.site, ext_param=response.meta['param']) # 随机休眠 time.sleep(random.randint(50, 100) / 1000.0) # 更新数据库中爬取数量 # self.crawl_helper.increase_total_item_num(crawl_key) logging.info('item is: {}'.format(item)) # yield item except Exception as e: logging.exception('Handle [{}] failed'.format(_detail_url))
class ShanghaiSpider(scrapy.Spider): name = 'ccgp_chongqing_spider' # 需要修改成全局唯一的名字 allowed_domains = ['www.ccgp-chongqing.gov.cn'] # 需要修改成爬取网站的域名 def __init__(self, *args, **kwargs): super(ShanghaiSpider, self).__init__(*args, **kwargs) self.num = 0 self.site = self.allowed_domains[0] self.crawl_mode = CrawlMode.HISTORY # 用来判断数据是否已经入库的mongo表对象 self.mongo_client = None self.mongo_col_obj = None self.crawl_helper = None # 日志相关 logging.basicConfig( level=logging.DEBUG, format='%(asctime)s %(filename)s : %(levelname)s %(message)s') # Define a RotatingFileHandler rf_handler = logging.handlers.RotatingFileHandler( filename='./{}.log'.format(self.name), mode='a', maxBytes=10 * 1024 * 1024, backupCount=20, ) formatter = logging.Formatter( '%(asctime)s %(filename)s:%(lineno)s : %(levelname)s %(message)s' ) rf_handler.setFormatter(formatter) rf_handler.setLevel(logging.INFO) # Create an root instance logging.getLogger().addHandler(rf_handler) def init_crawl_helper(self): try: if 'spider_name' in self.__dict__: spider_name = self.spider_name else: spider_name = self.name _settings = self.settings self.mongo_client = pymongo.MongoClient( _settings.get('MONGDB_URI')) self.crawl_helper = JyCrawlHelper( spider_name=spider_name, mongo_client=self.mongo_client, db_name=_settings.get('MONGDB_DB_NAME'), col_name=_settings.get('MONGDB_COLLECTION')) except Exception as e: logging.exception('Get get_crawl_helper failed') self.crawl_helper = None def start_requests(self): """ 爬虫默认接口,启动方法 :return: """ # 获取爬取时传过来的参数 # start_time: 开始时间 # end_time: 结束时间 # start_page: 开始页 (优先于start_time) # end_page: 结束页 (优先于end_time) # stop_item: 连续遇到[stop_item]个重复条目后,退出本次爬取 # spider_name: 指定的spider_name,如果不指定,使用self.name # command example: # python3 -m scrapy crawl ccgp_chongqing_spider -a start_time="2019:01:01" -a end_time="2020:02:26" # nohup python3 -m scrapy crawl ccgp_chongqing_spider -a start_time="2019:01:01" -a end_time="2020:02:25" > /dev/null& # py -3 -m scrapy crawl base_spider -a start_time="now" -a end_time="now" # py -3 -m scrapy crawl base_spider -a start_time="now" -a end_time="now" -a start_page="700" -a end_page="1000" -a stop_item="10000" assert self.start_time is not None assert self.end_time is not None self.crawl_mode = CrawlMode.REAL_TIME if str( self.start_time).lower() == 'now' else CrawlMode.HISTORY if self.crawl_mode == CrawlMode.HISTORY: if (len(self.start_time) != 10 or len(self.end_time) != 10 or self.start_time[4] != ':' or self.end_time[4] != ':'): logging.error( 'Bad date format start_time:[{}] end_time:[{}]. Example: 2019:01:01' .format(self.start_time, self.end_time)) return else: # 取当天日期 _dt = datetime.fromtimestamp(time.time()) self.start_time = _dt.strftime("%Y:%m:%d") self.end_time = self.start_time # 初始化self.crawl_helper self.init_crawl_helper() # 主要配置项 _source_info = { # 页面的key,保证唯一 'page_1': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '重庆政府采购网', # list页面的base地址 'base_url': 'https://www.ccgp-chongqing.gov.cn/gwebsite/api/v1/notices/stable/new?', # list页面的call_back处理函数 'callback': self.parse_list_page_common, 'method': "get", 'requests_type': "dict", # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 1000, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': ['notices'], # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': 'id', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '政府采购', 'tos_code': '02', 'source': '重庆政府采购网', 'notice_type': "采购公告", 'notice_type_code': '0201', 'area_code': '31', 'content_code': '1', 'site_name': '重庆政府采购网', 'industry': '政府采购', 'type': '100,200,201,202,203,204,205,206,207,309,400,401,402,3091,4001', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_2': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '重庆政府采购网', # list页面的base地址 'base_url': 'https://www.ccgp-chongqing.gov.cn/gwebsite/api/v1/notices/stable/new?', # list页面的call_back处理函数 'callback': self.parse_list_page_common, 'method': "get", 'requests_type': "dict", # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 700, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': ['notices'], # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': 'id', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '政府采购', 'tos_code': '02', 'source': '重庆政府采购网', 'notice_type': "采购结果公告", 'notice_type_code': '0202', 'area_code': '31', 'content_code': '1', 'site_name': '重庆政府采购网', 'industry': '政府采购', 'type': '300,302,304,3041,305,306,307,308', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, } logging.info('start crawling...') # 轮询每个类别 for _k, _v in _source_info.items(): # 填充爬取的基本信息 self.crawl_helper.init_crawl_info(_k, _v) # 假定每个类别有不超过100000个页面 for _page_num in range(100000): # 轮询公告中的不同list页面 if self.crawl_helper.get_stop_flag(_k): break # 根据获得下一页的函数,得到下一页的URL _ext_param = { 'type': _v['type'], 'start_time': self.start_time, 'end_time': self.end_time, } _request_url = _v['get_next_page_url'](page_index=_page_num, base_url=_v['base_url'], ext_param=_ext_param) # _request = "" # 生成request if _v["method"] == "post": payload = {} _request = scrapy.FormRequest(url=_request_url, body=payload, callback=_v['callback']) else: _request = scrapy.Request(_request_url, callback=_v['callback']) # 如果需要js渲染,需要使用下面的函数 # _request = SplashRequest(_request_url, callback=_v['callback'], args={'wait': 2}) # 填充必要的参数 _request.meta['param'] = _v _request.meta['crawl_key'] = _k _request.meta['page_index'] = _page_num + 1 yield _request # 单个类别的爬取结束 self.crawl_helper.stop_crawl_info(_k) logging.info('stop crawling...') def closed(self, reason): if self.mongo_client: self.mongo_client.close() self.crawl_helper.store_crawl_info_2_db(key=None, status='stopped', comment=reason) logging.info('Spider[{}] closed, reason:[{}]'.format( self.name, reason)) @staticmethod def get_normal_next_page_url(page_index, base_url, ext_param): _start_time = ext_param['start_time'].replace(':', '-') _end_time = ext_param['end_time'].replace(':', '-') _param = { 'endDate': _end_time, 'pi': page_index + 1, 'ps': '20', 'startDate': _start_time, 'type': ext_param['type'] } return '{}{}'.format(base_url, urlencode(_param, quote_via=quote_plus)) def parse_list_page_common(self, response): """ 通用版list页面解析 必要条件: :param response: :return: """ assert 'crawl_key' in response.meta assert 'page_index' in response.meta assert 'param' in response.meta assert 'xpath_of_list' in response.meta['param'] assert 'xpath_of_detail_url' in response.meta['param'] assert 'item_parse_class' in response.meta['param'] list_page_content_md5 = hashlib.md5(response.body).hexdigest() logging.info( 'Get page list url, page:[{}], url:[{}], status:[{}], body md5:[{}]' .format(response.meta['page_index'], response.url, response.status, list_page_content_md5)) logging.info('Crawl info: {}'.format(self.crawl_helper.crawl_info)) crawl_key = response.meta['crawl_key'] # 更新状态表记录 self.crawl_helper.store_crawl_info_2_db(crawl_key, 'active') if not self.crawl_helper.should_continue_page_parse( response, crawl_key, list_page_content_md5): return _item_idx = 0 if response.meta['param']['requests_type'] == "dict": _request = response.text.encode('utf-8') _response_data = json.loads(response.text) # _dict_xpath = response.meta['param']['xpath_of_list'].split("/") # if len(_dict_xpath) > 1: for _dictn_num in response.meta['param']["xpath_of_list"]: _response_data = _response_data[_dictn_num] for selector in _response_data: _detail_url = '' try: _item_idx += 1 _detail_url = 'https://www.ccgp-chongqing.gov.cn/notices/detail/' + str( selector['id']) + '?title=' + selector['title'] # _detail_url = response.urljoin( # selector[response.meta['param']['xpath_of_detail_url']] # ) _unq_id = JyScrapyUtil.get_unique_id(_detail_url) logging.info('Parse item, [{}]-[{}/{}]'.format( crawl_key, _item_idx, response.meta['page_index'])) # 检查记录是否已在库中,并做相应的跳出动作 loop_break, item_break = self.crawl_helper.should_continue_item_parse( crawl_key, _unq_id) if loop_break: return if item_break: continue # 生成并返回爬取item item_parser = response.meta['param']['item_parse_class']( selector) item = item_parser.get_common_raw_item( _id=_unq_id, detail_url=_detail_url, site=self.site, ext_param=response.meta['param']) # 随机休眠 time.sleep(random.randint(50, 100) / 1000.0) # 更新数据库中爬取数量 self.crawl_helper.increase_total_item_num(crawl_key) logging.info('item is: {}'.format(item)) yield item except Exception as e: logging.exception('Handle [{}] failed'.format(_detail_url)) else: for selector in response.xpath( response.meta['param']['xpath_of_list']): _detail_url = '' try: _item_idx += 1 _detail_url = response.urljoin( selector.xpath( response.meta['param'] ['xpath_of_detail_url']).extract_first()) _unq_id = JyScrapyUtil.get_unique_id(_detail_url) logging.info('Parse item, [{}]-[{}/{}]'.format( crawl_key, _item_idx, response.meta['page_index'])) # 检查记录是否已在库中,并做相应的跳出动作 loop_break, item_break = self.crawl_helper.should_continue_item_parse( crawl_key, _unq_id) if loop_break: return if item_break: continue # 生成并返回爬取item item_parser = response.meta['param']['item_parse_class']( selector) item = item_parser.get_common_raw_item( _id=_unq_id, detail_url=_detail_url, site=self.site, ext_param=response.meta['param']) # 随机休眠 time.sleep(random.randint(50, 100) / 1000.0) # 更新数据库中爬取数量 self.crawl_helper.increase_total_item_num(crawl_key) logging.info('item is: {}'.format(item)) yield item except Exception as e: logging.exception('Handle [{}] failed'.format(_detail_url))
class ZgjjzbcgglxxxtSpider(scrapy.Spider): name = 'zgjjzbcgglxxxt_spider' # 需要修改成全局唯一的名字 allowed_domains = ['sup.ccccltd.cn'] # 需要修改成爬取网站的域名 def __init__(self, *args, **kwargs): super(ZgjjzbcgglxxxtSpider, self).__init__(*args, **kwargs) self.num = 0 self.site = self.allowed_domains[0] self.crawl_mode = CrawlMode.HISTORY # 用来判断数据是否已经入库的mongo表对象 self.mongo_client = None self.mongo_col_obj = None self.crawl_helper = None # 日志相关 logging.basicConfig( level=logging.DEBUG, format='%(asctime)s %(filename)s : %(levelname)s %(message)s') # Define a RotatingFileHandler rf_handler = logging.handlers.RotatingFileHandler( filename='./{}.log'.format(self.name), mode='a', maxBytes=10 * 1024 * 1024, backupCount=20, ) formatter = logging.Formatter( '%(asctime)s %(filename)s:%(lineno)s : %(levelname)s %(message)s' ) rf_handler.setFormatter(formatter) rf_handler.setLevel(logging.INFO) # Create an root instance logging.getLogger().addHandler(rf_handler) def init_crawl_helper(self): try: if 'spider_name' in self.__dict__: spider_name = self.spider_name else: spider_name = self.name _settings = self.settings self.mongo_client = pymongo.MongoClient( _settings.get('MONGDB_URI')) self.crawl_helper = JyCrawlHelper( spider_name=spider_name, mongo_client=self.mongo_client, db_name=_settings.get('MONGDB_DB_NAME'), col_name=_settings.get('MONGDB_COLLECTION')) except Exception as e: logging.exception('Get get_crawl_helper failed') self.crawl_helper = None def start_requests(self): """ 爬虫默认接口,启动方法 :return: """ # 获取爬取时传过来的参数 # start_time: 开始时间 # end_time: 结束时间 # start_page: 开始页 (优先于start_time) # end_page: 结束页 (优先于end_time) # stop_item: 连续遇到[stop_item]个重复条目后,退出本次爬取 # spider_name: 指定的spider_name,如果不指定,使用self.name # command example: # python3 -m scrapy crawl zgjjzbcgglxxxt_spider -a start_time="2018:01:01" -a end_time="2019:01:02" # py -3 -m scrapy crawl base_spider -a start_time="now" -a end_time="now" # py -3 -m scrapy crawl base_spider -a start_time="now" -a end_time="now" -a start_page="700" -a end_page="1000" -a stop_item="10000" assert self.start_time is not None assert self.end_time is not None self.crawl_mode = CrawlMode.REAL_TIME if str( self.start_time).lower() == 'now' else CrawlMode.HISTORY if self.crawl_mode == CrawlMode.HISTORY: if (len(self.start_time) != 10 or len(self.end_time) != 10 or self.start_time[4] != ':' or self.end_time[4] != ':'): logging.error( 'Bad date format start_time:[{}] end_time:[{}]. Example: 2019:01:01' .format(self.start_time, self.end_time)) return else: # 取当天日期 _dt = datetime.fromtimestamp(time.time()) self.start_time = _dt.strftime("%Y:%m:%d") self.end_time = self.start_time # 初始化self.crawl_helper self.init_crawl_helper() # 主要配置项 _source_info = { # 页面的key,保证唯一 'page_1': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '中国交建装备采购管理信息系统', # list页面的base地址 'base_url': 'http://sup.ccccltd.cn/ecp/web/AnnounceAction.do?cmd=goListAnnounce', # list页面的call_back处理函数 'callback': self.parse_list_page_common, 'method': "post", 'requests_type': "html", # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 1000, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath # 'xpath_of_list': '//ul[@id="listId"]//li', 'xpath_of_list': '//li', # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': './span[@class="dotspan"]/a/@href', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '工程建设', 'tos_code': '01', 'source': '中国交建装备采购管理信息系统', 'notice_type': '采购公告', 'notice_type_code': '0101', 'classId': '152', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, } logging.info('start crawling...') # 轮询每个类别 for _k, _v in _source_info.items(): # 填充爬取的基本信息 self.crawl_helper.init_crawl_info(_k, _v) # 假定每个类别有不超过100000个页面 for _page_num in range(100000): # 轮询公告中的不同list页面 if self.crawl_helper.get_stop_flag(_k): break # 根据获得下一页的函数,得到下一页的URL _request_url = _v['get_next_page_url'](page_index=_page_num, base_url=_v['base_url']) # _request = "" # 生成request if _v["method"] == "post": _payload = { 'type': "0", 'VENUS_PAGE_NO_KEY': str(_page_num + 1), 'VENUS_PAGE_COUNT_KEY': "94", 'VENUS_PAGE_SIZE_KEY': "15", } _request = scrapy.FormRequest(url=_request_url, formdata=_payload, callback=_v['callback']) else: _request = scrapy.Request(_request_url, callback=_v['callback']) # 如果需要js渲染,需要使用下面的函数 # _request = SplashRequest(_request_url, callback=_v['callback'], args={'wait': 2}) # 填充必要的参数 _request.meta['param'] = _v _request.meta['crawl_key'] = _k _request.meta['page_index'] = _page_num + 1 yield _request # 单个类别的爬取结束 self.crawl_helper.stop_crawl_info(_k) logging.info('stop crawling...') def closed(self, reason): if self.mongo_client: self.mongo_client.close() self.crawl_helper.store_crawl_info_2_db(key=None, status='stopped', comment=reason) logging.info('Spider[{}] closed, reason:[{}]'.format( self.name, reason)) @staticmethod def get_normal_next_page_url(page_index, base_url): return '{}'.format(base_url) def parse_list_page_common(self, response): """ 通用版list页面解析 必要条件: :param response: :return: """ assert 'crawl_key' in response.meta assert 'page_index' in response.meta assert 'param' in response.meta assert 'xpath_of_list' in response.meta['param'] assert 'xpath_of_detail_url' in response.meta['param'] assert 'item_parse_class' in response.meta['param'] list_page_content_md5 = hashlib.md5(response.body).hexdigest() logging.info( 'Get page list url, page:[{}], url:[{}], status:[{}], body md5:[{}]' .format(response.meta['page_index'], response.url, response.status, list_page_content_md5)) logging.info('Crawl info: {}'.format(self.crawl_helper.crawl_info)) crawl_key = response.meta['crawl_key'] # 更新状态表记录 # self.crawl_helper.store_crawl_info_2_db(crawl_key, 'active') if not self.crawl_helper.should_continue_page_parse( response, crawl_key, list_page_content_md5): return _item_idx = 0 if response.meta['param']['requests_type'] == "dict": _request = response.text.encode('utf-8') _response_data = json.loads(response.text) # _dict_xpath = response.meta['param']['xpath_of_list'].split("/") # if len(_dict_xpath) > 1: for _dictn_num in response.meta['param']["xpath_of_list"]: _response_data = _response_data[_dictn_num] for selector in _response_data: _detail_url = '' try: _item_idx += 1 # _detail_url = response.urljoin( # selector.xpath(response.meta['param']['xpath_of_detail_url']).extract_first()) _detail_url = response.urljoin(selector[ response.meta['param']['xpath_of_detail_url']]) _unq_id = JyScrapyUtil.get_unique_id(_detail_url) logging.info('Parse item, [{}]-[{}/{}]'.format( crawl_key, _item_idx, response.meta['page_index'])) # 检查记录是否已在库中,并做相应的跳出动作 # loop_break, item_break = self.crawl_helper.should_continue_item_parse(crawl_key, _unq_id) # if loop_break: # return # if item_break: # continue # 生成并返回爬取item item_parser = response.meta['param']['item_parse_class']( selector) item = item_parser.get_common_raw_item( _id=_unq_id, detail_url=_detail_url, site=self.site, ext_param=response.meta['param']) # 随机休眠 time.sleep(random.randint(50, 100) / 1000.0) # 更新数据库中爬取数量 # self.crawl_helper.increase_total_item_num(crawl_key) logging.info('item is: {}'.format(item)) # yield item except Exception as e: logging.exception('Handle [{}] failed'.format(_detail_url)) else: for selector in response.xpath( response.meta['param']['xpath_of_list']): _detail_url = '' try: _item_idx += 1 _detail_url = response.urljoin( selector.xpath( response.meta['param'] ['xpath_of_detail_url']).extract_first()) _unq_id = JyScrapyUtil.get_unique_id(_detail_url) logging.info('Parse item, [{}]-[{}/{}]'.format( crawl_key, _item_idx, response.meta['page_index'])) # 检查记录是否已在库中,并做相应的跳出动作 # loop_break, item_break = self.crawl_helper.should_continue_item_parse(crawl_key, _unq_id) # if loop_break: # return # if item_break: # continue # 生成并返回爬取item item_parser = response.meta['param']['item_parse_class']( selector) item = item_parser.get_common_raw_item( _id=_unq_id, detail_url=_detail_url, site=self.site, ext_param=response.meta['param']) # 随机休眠 time.sleep(random.randint(50, 100) / 1000.0) # 更新数据库中爬取数量 # self.crawl_helper.increase_total_item_num(crawl_key) logging.info('item is: {}'.format(item)) # yield item except Exception as e: logging.exception('Handle [{}] failed'.format(_detail_url))
class Ccgp_guizhou_Spider(scrapy.Spider): name = 'ccgp_guizhou_spider' # 需要修改成全局唯一的名字 allowed_domains = ['www.ccgp-guizhou.gov.cn'] # 需要修改成爬取网站的域名 def __init__(self, *args, **kwargs): super(Ccgp_guizhou_Spider, self).__init__(*args, **kwargs) self.num = 0 self.site = self.allowed_domains[0] self.crawl_mode = CrawlMode.HISTORY # 用来判断数据是否已经入库的mongo表对象 self.mongo_client = None self.mongo_col_obj = None self.crawl_helper = None # 日志相关 logging.basicConfig( level=logging.DEBUG, format='%(asctime)s %(filename)s : %(levelname)s %(message)s') # Define a RotatingFileHandler rf_handler = logging.handlers.RotatingFileHandler( filename='./{}.log'.format(self.name), mode='a', maxBytes=10 * 1024 * 1024, backupCount=20, ) formatter = logging.Formatter( '%(asctime)s %(filename)s:%(lineno)s : %(levelname)s %(message)s' ) rf_handler.setFormatter(formatter) rf_handler.setLevel(logging.INFO) # Create an root instance logging.getLogger().addHandler(rf_handler) def init_crawl_helper(self): try: if 'spider_name' in self.__dict__: spider_name = self.spider_name else: spider_name = self.name _settings = self.settings self.mongo_client = pymongo.MongoClient( _settings.get('MONGDB_URI')) self.crawl_helper = JyCrawlHelper( spider_name=spider_name, mongo_client=self.mongo_client, db_name=_settings.get('MONGDB_DB_NAME'), col_name=_settings.get('MONGDB_COLLECTION')) except Exception as e: logging.exception('Get get_crawl_helper failed') self.crawl_helper = None def start_requests(self): """ 爬虫默认接口,启动方法 :return: """ # 获取爬取时传过来的参数 # start_time: 开始时间 # end_time: 结束时间 # start_page: 开始页 (优先于start_time) # end_page: 结束页 (优先于end_time) # stop_item: 连续遇到[stop_item]个重复条目后,退出本次爬取 # spider_name: 指定的spider_name,如果不指定,使用self.name # command example: # nohup python3 -m scrapy crawl ccgp_guizhou_spider -a start_time="2019:01:01" -a end_time="2020:02:25" > /dev/null& # py -3 -m scrapy crawl base_spider -a start_time="now" -a end_time="now" # py -3 -m scrapy crawl base_spider -a start_time="now" -a end_time="now" -a start_page="700" -a end_page="1000" -a stop_item="10000" assert self.start_time is not None assert self.end_time is not None self.crawl_mode = CrawlMode.REAL_TIME if str( self.start_time).lower() == 'now' else CrawlMode.HISTORY if self.crawl_mode == CrawlMode.HISTORY: if (len(self.start_time) != 10 or len(self.end_time) != 10 or self.start_time[4] != ':' or self.end_time[4] != ':'): logging.error( 'Bad date format start_time:[{}] end_time:[{}]. Example: 2019:01:01' .format(self.start_time, self.end_time)) return else: # 取当天日期 _dt = datetime.fromtimestamp(time.time()) self.start_time = _dt.strftime("%Y:%m:%d") self.end_time = self.start_time # 初始化self.crawl_helper self.init_crawl_helper() # 主要配置项 _source_info = { # 页面的key,保证唯一 'page_1': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '贵州省政府采购网', # list页面的base地址 'base_url': 'http://www.ccgp-guizhou.gov.cn/article-search.html', # list页面的call_back处理函数 'callback': self.parse_list_page_common, 'method': "post", 'requests_type': "html", # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 1000, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': '//div[@class="xnrx"]/ul/li', # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': './a/@href', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '政府采购', 'tos_code': '02', 'source': '贵州省政府采购网', 'notice_type': '采购需求公告', 'notice_type_code': '0201', 'site_name': '贵州省政府采购网', 'area_code': '52', 'content_code': '1', 'industryName': '', 'category.id': '1153332561072666', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_2': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '贵州省政府采购网', # list页面的base地址 'base_url': 'http://www.ccgp-guizhou.gov.cn/article-search.html', # list页面的call_back处理函数 'callback': self.parse_list_page_common, 'method': "post", 'requests_type': "html", # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 1000, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': '//div[@class="xnrx"]/ul/li', # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': './a/@href', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '政府采购', 'tos_code': '02', 'source': '贵州省政府采购网', 'notice_type': '采购公告', 'notice_type_code': '0201', 'site_name': '贵州省政府采购网', 'area_code': '52', 'content_code': '1', 'industryName': '', 'category.id': '1153418052184995', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_3': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '贵州省政府采购网', # list页面的base地址 'base_url': 'http://www.ccgp-guizhou.gov.cn/article-search.html', # list页面的call_back处理函数 'callback': self.parse_list_page_common, 'method': "post", 'requests_type': "html", # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 1000, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': '//div[@class="xnrx"]/ul/li', # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': './a/@href', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '政府采购', 'tos_code': '02', 'source': '贵州省政府采购网', 'notice_type': '更正公告', 'notice_type_code': '0204', 'site_name': '贵州省政府采购网', 'area_code': '52', 'content_code': '1', 'industryName': '', 'category.id': '1153454200156791', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_4': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '贵州省政府采购网', # list页面的base地址 'base_url': 'http://www.ccgp-guizhou.gov.cn/article-search.html', # list页面的call_back处理函数 'callback': self.parse_list_page_common, 'method': "post", 'requests_type': "html", # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 1000, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': '//div[@class="xnrx"]/ul/li', # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': './a/@href', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '政府采购', 'tos_code': '02', 'source': '贵州省政府采购网', 'notice_type': '废标公告', 'notice_type_code': '0204', 'site_name': '贵州省政府采购网', 'area_code': '52', 'content_code': '1', 'industryName': '', 'category.id': '1153488085289816', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_4': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '贵州省政府采购网', # list页面的base地址 'base_url': 'http://www.ccgp-guizhou.gov.cn/article-search.html', # list页面的call_back处理函数 'callback': self.parse_list_page_common, 'method': "post", 'requests_type': "html", # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 1000, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': '//div[@class="xnrx"]/ul/li', # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': './a/@href', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '政府采购', 'tos_code': '02', 'source': '贵州省政府采购网', 'notice_type': '中标(成交)公告', 'notice_type_code': '0202', 'site_name': '贵州省政府采购网', 'area_code': '52', 'content_code': '1', 'industryName': '', 'category.id': '1153531755759540', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_5': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '贵州省政府采购网', # list页面的base地址 'base_url': 'http://www.ccgp-guizhou.gov.cn/article-search.html', # list页面的call_back处理函数 'callback': self.parse_list_page_common, 'method': "post", 'requests_type': "html", # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 1000, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': '//div[@class="xnrx"]/ul/li', # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': './a/@href', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '政府采购', 'tos_code': '02', 'source': '贵州省政府采购网', 'notice_type': '单一来源公告', 'notice_type_code': '0201', 'site_name': '贵州省政府采购网', 'area_code': '52', 'content_code': '1', 'industryName': '', 'category.id': '1153567415242344', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_7': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '贵州省政府采购网', # list页面的base地址 'base_url': 'http://www.ccgp-guizhou.gov.cn/article-search.html', # list页面的call_back处理函数 'callback': self.parse_list_page_common, 'method': "post", 'requests_type': "html", # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 1000, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': '//div[@class="xnrx"]/ul/li', # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': './a/@href', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '政府采购', 'tos_code': '02', 'source': '贵州省政府采购网', 'notice_type': '单一来源(成交)公告', 'notice_type_code': '0202', 'site_name': '贵州省政府采购网', 'area_code': '52', 'content_code': '1', 'industryName': '', 'category.id': '1153595823404526', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, # 市县标讯 'page_8': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '贵州省政府采购网', # list页面的base地址 'base_url': 'http://www.ccgp-guizhou.gov.cn/article-search.html', # list页面的call_back处理函数 'callback': self.parse_list_page_common, 'method': "post", 'requests_type': "html", # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 1000, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': '//div[@class="xnrx"]/ul/li', # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': './a/@href', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '政府采购', 'tos_code': '02', 'source': '贵州省政府采购网', 'notice_type': '单一来源(成交)公告', 'notice_type_code': '0202', 'site_name': '贵州省政府采购网', 'area_code': '52', 'content_code': '1', 'industryName': '', 'category.id': '1153796890012888', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_9': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '贵州省政府采购网', # list页面的base地址 'base_url': 'http://www.ccgp-guizhou.gov.cn/article-search.html', # list页面的call_back处理函数 'callback': self.parse_list_page_common, 'method': "post", 'requests_type': "html", # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 1000, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': '//div[@class="xnrx"]/ul/li', # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': './a/@href', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '政府采购', 'tos_code': '02', 'source': '贵州省政府采购网', 'notice_type': '采购需求公告', 'notice_type_code': '0201', 'site_name': '贵州省政府采购网', 'area_code': '52', 'content_code': '1', 'industryName': '', 'category.id': '1153796890012888', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_10': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '贵州省政府采购网', # list页面的base地址 'base_url': 'http://www.ccgp-guizhou.gov.cn/article-search.html', # list页面的call_back处理函数 'callback': self.parse_list_page_common, 'method': "post", 'requests_type': "html", # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 1000, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': '//div[@class="xnrx"]/ul/li', # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': './a/@href', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '政府采购', 'tos_code': '02', 'source': '贵州省政府采购网', 'notice_type': '采购公告', 'notice_type_code': '0201', 'site_name': '贵州省政府采购网', 'area_code': '52', 'content_code': '1', 'industryName': '', 'category.id': '1153796890012888', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_11': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '贵州省政府采购网', # list页面的base地址 'base_url': 'http://www.ccgp-guizhou.gov.cn/article-search.html', # list页面的call_back处理函数 'callback': self.parse_list_page_common, 'method': "post", 'requests_type': "html", # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 1000, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': '//div[@class="xnrx"]/ul/li', # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': './a/@href', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '政府采购', 'tos_code': '02', 'source': '贵州省政府采购网', 'notice_type': '采购公告', 'notice_type_code': '0201', 'site_name': '贵州省政府采购网', 'area_code': '52', 'content_code': '1', 'industryName': '', 'category.id': '1153797950913584', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_12': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '贵州省政府采购网', # list页面的base地址 'base_url': 'http://www.ccgp-guizhou.gov.cn/article-search.html', # list页面的call_back处理函数 'callback': self.parse_list_page_common, 'method': "post", 'requests_type': "html", # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 1000, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': '//div[@class="xnrx"]/ul/li', # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': './a/@href', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '政府采购', 'tos_code': '02', 'source': '贵州省政府采购网', 'notice_type': '更正公告', 'notice_type_code': '0204', 'site_name': '贵州省政府采购网', 'area_code': '52', 'content_code': '1', 'industryName': '', 'category.id': '1153817836808214', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_13': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '贵州省政府采购网', # list页面的base地址 'base_url': 'http://www.ccgp-guizhou.gov.cn/article-search.html', # list页面的call_back处理函数 'callback': self.parse_list_page_common, 'method': "post", 'requests_type': "html", # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 1000, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': '//div[@class="xnrx"]/ul/li', # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': './a/@href', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '政府采购', 'tos_code': '02', 'source': '贵州省政府采购网', 'notice_type': '废标公告', 'notice_type_code': '0202', 'site_name': '贵州省政府采购网', 'area_code': '52', 'content_code': '1', 'industryName': '', 'category.id': '1153845808113747', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_14': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '贵州省政府采购网', # list页面的base地址 'base_url': 'http://www.ccgp-guizhou.gov.cn/article-search.html', # list页面的call_back处理函数 'callback': self.parse_list_page_common, 'method': "post", 'requests_type': "html", # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 1000, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': '//div[@class="xnrx"]/ul/li', # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': './a/@href', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '政府采购', 'tos_code': '02', 'source': '贵州省政府采购网', 'notice_type': '中标(成交)公告', 'notice_type_code': '0202', 'site_name': '贵州省政府采购网', 'area_code': '52', 'content_code': '1', 'industryName': '', 'category.id': '1153905922931045', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_15': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '贵州省政府采购网', # list页面的base地址 'base_url': 'http://www.ccgp-guizhou.gov.cn/article-search.html', # list页面的call_back处理函数 'callback': self.parse_list_page_common, 'method': "post", 'requests_type': "html", # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 1000, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': '//div[@class="xnrx"]/ul/li', # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': './a/@href', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '政府采购', 'tos_code': '02', 'source': '贵州省政府采购网', 'notice_type': '单一来源公示', 'notice_type_code': '0201', 'site_name': '贵州省政府采购网', 'area_code': '52', 'content_code': '1', 'industryName': '', 'category.id': '1153924595764135', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_16': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '贵州省政府采购网', # list页面的base地址 'base_url': 'http://www.ccgp-guizhou.gov.cn/article-search.html', # list页面的call_back处理函数 'callback': self.parse_list_page_common, 'method': "post", 'requests_type': "html", # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 1000, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': '//div[@class="xnrx"]/ul/li', # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': './a/@href', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '政府采购', 'tos_code': '02', 'source': '贵州省政府采购网', 'notice_type': '单一来源(成交)公示', 'notice_type_code': '0202', 'site_name': '贵州省政府采购网', 'area_code': '52', 'content_code': '1', 'industryName': '', 'category.id': '1153937977184763', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_17': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '贵州省政府采购网', # list页面的base地址 'base_url': 'http://www.ccgp-guizhou.gov.cn/article-search.html', # list页面的call_back处理函数 'callback': self.parse_list_page_common, 'method': "post", 'requests_type': "html", # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 1000, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': '//div[@class="xnrx"]/ul/li', # 获得每一个条目链接地址的xpath 'xpath_of_detail_url': './a/@href', # 对每一个条目进行解析,返回CommonRawItem的类,需要实现 'item_parse_class': BaseItemCommonParser, # 其它信息,可以辅助生成CommonRawItem的字段 # 参考函数parse_list_page_common() 中 item_parser.get_common_raw_item()代码 'tos': '政府采购', 'tos_code': '02', 'source': '贵州省政府采购网', 'notice_type': '资格预审公告', 'notice_type_code': '0201', 'site_name': '贵州省政府采购网', 'area_code': '52', 'content_code': '1', 'industryName': '', 'category.id': '1156071132710859', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, } logging.info('start crawling...') # 轮询每个类别 for _k, _v in _source_info.items(): # 填充爬取的基本信息 self.crawl_helper.init_crawl_info(_k, _v) # 假定每个类别有不超过100000个页面 for _page_num in range(100000): # 轮询公告中的不同list页面 if self.crawl_helper.get_stop_flag(_k): break # 根据获得下一页的函数,得到下一页的URL _request_url = _v['get_next_page_url'](page_index=_page_num, base_url=_v['base_url']) # _request = "" # 生成request if _v["method"] == "post": _payload = { 'siteId': "1", 'category.id': _v["category.id"], 'areaName': "", 'tenderRocurementPm': "", 'keywords': "", 'articlePageNo': str(_page_num + 1), 'articlePageSize': "15" } _request = scrapy.FormRequest(url=_request_url, formdata=_payload, callback=_v['callback']) else: _request = scrapy.Request(_request_url, callback=_v['callback']) # 如果需要js渲染,需要使用下面的函数 # _request = SplashRequest(_request_url, callback=_v['callback'], args={'wait': 2}) # 填充必要的参数 _request.meta['param'] = _v _request.meta['crawl_key'] = _k _request.meta['page_index'] = _page_num + 1 yield _request # 单个类别的爬取结束 self.crawl_helper.stop_crawl_info(_k) logging.info('stop crawling...') def closed(self, reason): if self.mongo_client: self.mongo_client.close() self.crawl_helper.store_crawl_info_2_db(key=None, status='stopped', comment=reason) logging.info('Spider[{}] closed, reason:[{}]'.format( self.name, reason)) @staticmethod def get_normal_next_page_url(page_index, base_url): return '{}'.format(base_url) def parse_list_page_common(self, response): """ 通用版list页面解析 必要条件: :param response: :return: """ assert 'crawl_key' in response.meta assert 'page_index' in response.meta assert 'param' in response.meta assert 'xpath_of_list' in response.meta['param'] assert 'xpath_of_detail_url' in response.meta['param'] assert 'item_parse_class' in response.meta['param'] list_page_content_md5 = hashlib.md5(response.body).hexdigest() logging.info( 'Get page list url, page:[{}], url:[{}], status:[{}], body md5:[{}]' .format(response.meta['page_index'], response.url, response.status, list_page_content_md5)) logging.info('Crawl info: {}'.format(self.crawl_helper.crawl_info)) crawl_key = response.meta['crawl_key'] # 更新状态表记录 self.crawl_helper.store_crawl_info_2_db(crawl_key, 'active') if not self.crawl_helper.should_continue_page_parse( response, crawl_key, list_page_content_md5): return _item_idx = 0 if response.meta['param']['requests_type'] == "dict": _request = response.text.encode('utf-8') _response_data = json.loads(response.text) # _dict_xpath = response.meta['param']['xpath_of_list'].split("/") # if len(_dict_xpath) > 1: for _dictn_num in response.meta['param']["xpath_of_list"]: _response_data = _response_data[_dictn_num] for selector in _response_data: _detail_url = '' try: _item_idx += 1 # _detail_url = response.urljoin( # selector.xpath(response.meta['param']['xpath_of_detail_url']).extract_first()) _detail_url = response.urljoin(selector[ response.meta['param']['xpath_of_detail_url']]) _unq_id = JyScrapyUtil.get_unique_id(_detail_url) logging.info('Parse item, [{}]-[{}/{}]'.format( crawl_key, _item_idx, response.meta['page_index'])) # 检查记录是否已在库中,并做相应的跳出动作 loop_break, item_break = self.crawl_helper.should_continue_item_parse( crawl_key, _unq_id) if loop_break: return if item_break: continue # 生成并返回爬取item item_parser = response.meta['param']['item_parse_class']( selector) item = item_parser.get_common_raw_item( _id=_unq_id, detail_url=_detail_url, site=self.site, ext_param=response.meta['param']) # 随机休眠 time.sleep(random.randint(50, 100) / 1000.0) # 更新数据库中爬取数量 self.crawl_helper.increase_total_item_num(crawl_key) logging.info('item is: {}'.format(item)) yield item except Exception as e: logging.exception('Handle [{}] failed'.format(_detail_url)) else: for selector in response.xpath( response.meta['param']['xpath_of_list']): _detail_url = '' try: _item_idx += 1 _detail_url = response.urljoin( selector.xpath( response.meta['param'] ['xpath_of_detail_url']).extract_first()) _unq_id = JyScrapyUtil.get_unique_id(_detail_url) logging.info('Parse item, [{}]-[{}/{}]'.format( crawl_key, _item_idx, response.meta['page_index'])) # 检查记录是否已在库中,并做相应的跳出动作 loop_break, item_break = self.crawl_helper.should_continue_item_parse( crawl_key, _unq_id) if loop_break: return if item_break: continue # 生成并返回爬取item item_parser = response.meta['param']['item_parse_class']( selector) item = item_parser.get_common_raw_item( _id=_unq_id, detail_url=_detail_url, site=self.site, ext_param=response.meta['param']) # 随机休眠 time.sleep(random.randint(50, 100) / 1000.0) # 更新数据库中爬取数量 self.crawl_helper.increase_total_item_num(crawl_key) logging.info('item is: {}'.format(item)) yield item except Exception as e: logging.exception('Handle [{}] failed'.format(_detail_url))