def __get_content__(self, url): """ 正文内容 如果提取正文内容失败,则判断此次爬取失败,所以这里不能用try except :param url: :return: """ _bad = False _ret = '' try: # _r = requests.get(url, timeout=15) _r = JyScrapyUtil.request_html_by_proxy(url) _r.encoding = 'utf-8' _ret = base64.b64encode(zlib.compress(_r.text.encode('utf-8'))).decode('utf-8') except: _bad = True # 如果有异常,重试一次 if _bad: time.sleep(1) _r = JyScrapyUtil.request_html_by_proxy(url) _r.encoding = 'utf-8' _ret = base64.b64encode(zlib.compress(_r.text.encode('utf-8'))).decode('utf-8') return _ret
def should_continue_item_parse(self, key, item_id): """ 根据item_id判断是否需要进行item存储,同时记录item状态 :param key: :param item_id: :return loop_break: 是否要跳出外层的for循环; item_break:是否要忽略本item """ loop_break, item_break = False, False if JyScrapyUtil.is_record_in_mongo(item_id, self.mongo_col_obj): logging.info('Duplicate item:[{}] total:[{}-{}]'.format( item_id, key, self.get_duplicate_item_num(key))) self.increase_duplicate_item_num(key) # 如果连续n条重复数据,认为本次爬取已可以结束 if self.get_duplicate_item_num(key) >= self.get_stop_dup_item_num( key): logging.warning( 'duplicate items[{}-{}] reach limit, stop crawl'.format( key, self.get_duplicate_item_num(key))) self.set_stop_flag(key) loop_break = True item_break = True else: self.set_duplicate_item_num(key, 0) return loop_break, item_break
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))
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: # py -3 -m scrapy crawl base_spider -a start_time="2019:01:01" -a end_time="2019:01:02" # python3 -m scrapy crawl bjggzyjy_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://ggzyfw.beijing.gov.cn/', # list页面的call_back处理函数 'callback': self.parse_list_page_common, # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 7000000, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': '//ul[@class="article-list2"]/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': '01', 'notice_type': '招标计划', 'notice_type_code': '0101', 'source': '北京公共资源交易网', 'site_name': '北京公共资源交易网', 'area_code': '11', 'content_code': '1', 'bid_sort': 'jyxxgcjszbjh', 'industryName': '', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_2': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '北京公共资源交易网', # list页面的base地址 'base_url': 'https://ggzyfw.beijing.gov.cn/', # list页面的call_back处理函数 'callback': self.parse_list_page_common, # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 7000000, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': '//ul[@class="article-list2"]/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': '01', 'notice_type': '招标公告', 'notice_type_code': '0101', 'source': '北京公共资源交易网', 'site_name': '北京公共资源交易网', 'area_code': '11', 'content_code': '1', 'bid_sort': 'jyxxggjtbyqs', 'industryName': '', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_3': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '北京公共资源交易网', # list页面的base地址 'base_url': 'https://ggzyfw.beijing.gov.cn/', # list页面的call_back处理函数 'callback': self.parse_list_page_common, # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 7000000, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': '//ul[@class="article-list2"]/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': '01', 'notice_type': '中标候选人公示', 'notice_type_code': '0104', 'source': '北京公共资源交易网', 'site_name': '北京公共资源交易网', 'area_code': '11', 'content_code': '1', 'bid_sort': 'jyxxzbhxrgs', 'industryName': '', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_4': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '北京公共资源交易网', # list页面的base地址 'base_url': 'https://ggzyfw.beijing.gov.cn/', # list页面的call_back处理函数 'callback': self.parse_list_page_common, # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 7000000, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': '//ul[@class="article-list2"]/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': '01', 'notice_type': '中标结果', 'notice_type_code': '0104', 'source': '北京公共资源交易网', 'site_name': '北京公共资源交易网', 'area_code': '11', 'content_code': '1', 'bid_sort': 'jyxxzbgg', 'industryName': '', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_5': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '北京公共资源交易网', # list页面的base地址 'base_url': 'https://ggzyfw.beijing.gov.cn/', # list页面的call_back处理函数 'callback': self.parse_list_page_common, # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 7000000, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': '//ul[@class="article-list2"]/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': '0101', 'source': '北京公共资源交易网', 'site_name': '北京公共资源交易网', 'area_code': '11', 'content_code': '1', 'bid_sort': 'jyxxcggg', 'industryName': '', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_6': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '北京公共资源交易网', # list页面的base地址 'base_url': 'https://ggzyfw.beijing.gov.cn/', # list页面的call_back处理函数 'callback': self.parse_list_page_common, # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 7000000, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': '//ul[@class="article-list2"]/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': '0204', 'source': '北京公共资源交易网', 'site_name': '北京公共资源交易网', 'area_code': '11', 'content_code': '1', 'bid_sort': 'jyxxgzsx', 'industryName': '', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_7': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '北京公共资源交易网', # list页面的base地址 'base_url': 'https://ggzyfw.beijing.gov.cn/', # list页面的call_back处理函数 'callback': self.parse_list_page_common, # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 7000000, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': '//ul[@class="article-list2"]/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': '0202', 'source': '北京公共资源交易网', 'site_name': '北京公共资源交易网', 'area_code': '11', 'content_code': '1', 'bid_sort': 'jyxxzbjggg', 'industryName': '', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_8': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '北京公共资源交易网', # list页面的base地址 'base_url': 'https://ggzyfw.beijing.gov.cn/', # list页面的call_back处理函数 'callback': self.parse_list_page_common, # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 7000000, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': '//ul[@class="article-list2"]/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': '03', 'notice_type': '招拍挂公告', 'notice_type_code': '0301', 'source': '北京公共资源交易网', 'site_name': '北京公共资源交易网', 'area_code': '11', 'content_code': '1', 'bid_sort': 'jyxxzpggg', 'industryName': '', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_9': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '北京公共资源交易网', # list页面的base地址 'base_url': 'https://ggzyfw.beijing.gov.cn/', # list页面的call_back处理函数 'callback': self.parse_list_page_common, # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 7000000, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': '//ul[@class="article-list2"]/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': '03', 'notice_type': '招拍挂结果公示', 'notice_type_code': '0302', 'source': '北京公共资源交易网', 'site_name': '北京公共资源交易网', 'area_code': '11', 'content_code': '1', 'bid_sort': 'jyxxzpgjggs', 'industryName': '', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_10': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '北京公共资源交易网', # list页面的base地址 'base_url': 'https://ggzyfw.beijing.gov.cn/', # list页面的call_back处理函数 'callback': self.parse_list_page_common, # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 7000000, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': '//ul[@class="article-list2"]/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': '05', 'notice_type': '挂牌披露信息', 'notice_type_code': '0501', 'source': '北京公共资源交易网', 'site_name': '北京公共资源交易网', 'area_code': '11', 'content_code': '1', 'bid_sort': 'jyxxswzcgpplxx', 'industryName': '', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_11': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '北京公共资源交易网', # list页面的base地址 'base_url': 'https://ggzyfw.beijing.gov.cn/', # list页面的call_back处理函数 'callback': self.parse_list_page_common, # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 7000000, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': '//ul[@class="article-list2"]/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': '05', 'notice_type': '交易结果', 'notice_type_code': '0502', 'source': '北京公共资源交易网', 'site_name': '北京公共资源交易网', 'area_code': '11', 'content_code': '1', 'bid_sort': 'jyxxswzcjyjg', 'industryName': '', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_12': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '北京公共资源交易网', # list页面的base地址 'base_url': 'https://ggzyfw.beijing.gov.cn/', # list页面的call_back处理函数 'callback': self.parse_list_page_common, # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 7000000, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': '//ul[@class="article-list2"]/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': '90', 'notice_type': '招标公告', 'notice_type_code': '9001', 'source': '北京公共资源交易网', 'site_name': '北京公共资源交易网', 'area_code': '11', 'content_code': '1', 'bid_sort': 'jyxxrjxxzbgg', 'industryName': '', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_13': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '北京公共资源交易网', # list页面的base地址 'base_url': 'https://ggzyfw.beijing.gov.cn/', # list页面的call_back处理函数 'callback': self.parse_list_page_common, # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 7000000, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': '//ul[@class="article-list2"]/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': '90', 'notice_type': '中标候选人公示', 'notice_type_code': '9002', 'source': '北京公共资源交易网', 'site_name': '北京公共资源交易网', 'area_code': '11', 'content_code': '1', 'bid_sort': 'jyxxrjxxzbhx', 'industryName': '', 'time_type': 6 if self.crawl_mode == CrawlMode.HISTORY else 0, }, 'page_14': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '北京公共资源交易网', # list页面的base地址 'base_url': 'https://ggzyfw.beijing.gov.cn/', # list页面的call_back处理函数 'callback': self.parse_list_page_common, # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 7000000, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath 'xpath_of_list': '//ul[@class="article-list2"]/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': '90', 'notice_type': '中标结果', 'notice_type_code': '9002', 'source': '北京公共资源交易网', 'site_name': '北京公共资源交易网', 'area_code': '11', 'content_code': '1', 'bid_sort': 'jyxxrjxxjyjg', '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 = { 'time_type': _v['time_type'], 'bid_sort': _v['bid_sort'], '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 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))
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))
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: # py -3 -m scrapy crawl base_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" # python3 -m scrapy crawl southern_power_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,保证唯一 'zbgg_search': { # 通常会被填充在'source'字段里,有时也可以放在'tos' 'name': '南方电网招标', # list页面的base地址 'base_url': 'http://www.bidding.csg.cn/zbgg/', # list页面的call_back处理函数 'callback': self.parse_list_page_common, # 得到下一页url的函数,返回值一定是一个url 'get_next_page_url': self.get_normal_next_page_url, # 网站中该页面的最大页数,(可选配置,仅为优化程序执行效率,可不填) 'stop_page_num': 7000000, # 连续遇到[stop_dup_item_num]个重复条目后,停止本次抓取 # 提示:在程序运行初始阶段,此值可以设的较大,以便爬取所有的历史记录 'stop_dup_item_num': 500000 if self.crawl_mode == CrawlMode.HISTORY else 60, # list页面中,获得条目列表的xpath # 'xpath_of_list': '//ul[@class="vT-srch-result-list-bid"]/li', 'xpath_of_list': '//div[@class="BorderEEE NoBorderTop List1 Black14 Padding5"]/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': '企业采购', 'source': '招标公告', # 'bid_sort': 1, '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 # 填充爬取的基本信息 print(_k) print(_v) 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 = { 'time_type': _v['time_type'], # 'bid_sort': _v['bid_sort'], '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...')