def get_url_html(url): ''' 获取url的html超文本 @param url: url地址 @type url: 字符串 @return: 成功则返回html,失败则触发异常 @rtype: 字符串 ''' # 自定义header customHeader = dict() customHeader['User-Agent'] = Config.KOALA_USER_AGENT # 网络出错重试机制 retryTimes = 0 while True: try: # 先发送head做检测工作 rsp = requests.head(url, headers = customHeader) if not rsp.ok: rsp.raise_for_status() if not rsp.headers['content-type'].startswith('text/html'): raise TypeError('Specified url do not return HTML file') rsp = requests.get(url, headers = customHeader) return Common.to_unicode(rsp.content) except requests.exceptions.RequestException as e: Common.write_stderr(repr(e)) if retryTimes < Config.NETWORK_ERROR_MAX_RETRY_TIMES: retryTimes += 1 time.sleep(Config.NETWORK_ERROR_WAIT_SECOND) else: raise
def __init__(self, webSiteURL, entryFilter = None, yieldFilter = None, identifier = None, enableStatusSupport = False): ''' @param webSiteURL: 网站url地址 @type webSiteURL: 字符串 @param entryFilter: 入口过滤器,指定了爬虫可以进入哪些url。定义为字典,详情如下: ['Type'] 'allow' 允许模式,只要满足过滤项目表中的任意一项即允许 'deny' 排除模式,只要满足过滤项目表中的任意一项即排除 ['List'] 过滤项目列表,每一项均为正则表达式,字符串 @type entryFilter: 字典 @param yieldFilter: 生成过滤器,指定了爬虫要生成哪些url。定义为字典,详情如下: ['Type'] 'allow' 允许模式,只要满足过滤项目表中的任意一项即允许 'deny' 排除模式,只要满足过滤项目表中的任意一项即排除 ['List'] 过滤项目列表,每一项均为正则表达式,字符串 @type yieldFilter: 字典 @param identifier: 爬虫的id,用来标识爬虫 @type identifier: 字符串 @param enableStatusSupport: 是否启用状态支持,默认不启用 @type enableStatusSupport: 布尔值 ''' if not webSiteURL: raise ValueError('You must specified "webSiteURL" parameter in constructor') webSiteURL = Common.to_unicode(webSiteURL) # 如果url没有协议前缀,则使用默认协议前缀 webSiteURL = ensure_url_default_scheme(webSiteURL) self.domain = get_domain(webSiteURL) self.webSiteURL = webSiteURL self.entryFilter = entryFilter self.yieldFilter = yieldFilter # 如果没有指定id,则生成uuid if not identifier: self.identifier = str(uuid.uuid1()) else: self.identifier = identifier # 是否启用状态支持的标记 if not enableStatusSupport: self.koalaStatus = None else: self.koalaStatus = KoalaStatus(Common.hash(self.webSiteURL)) # 记录访问过的页面 self.visitedEntriesHash = set()
def __crawl_proc(self, entryURL, maxDepth): ''' 爬行的执行过程 @param entryURL: 爬虫的入口url @type entryURL: 字符串 @param maxDepth: 最大抓取深度 @type maxDepth: 整数 @return: 满足过滤条件的url @rtype: 字符串 ''' # 如果达到最大深度则返回 if maxDepth <= 0: return # 解析出页面中所有的链接 try: source = get_url_html(entryURL) soup = BeautifulSoup(source, Config.DEFAULT_HTML_PARSER) except Exception as e: Common.write_stderr(repr(e)) return links = list() for a in soup.find_all('a'): try: links.append(a['href'].encode(Common.UTF8_CHARSET_NAME)) except KeyError as e: Common.write_stderr(repr(e)) links = [Common.to_unicode(l) for l in links] # 生成符合规则的链接,并记录符合规则的子页面 nextEntries = list() for link in links: url = urlparse.urljoin(entryURL, link) if self.__global_filter(entryURL, url): if self.__yield_filter(url): yield url if self.__entry_filter(url): nextEntries.append(url) # 执行到此处代表一个(子)页面(EntryURL)处理完成 # 需要记录到已处理页面集合中。处于性能考虑,记录url的hash值而非url本身 self.visitedEntriesHash.add(Common.hash(entryURL)) # 如果启用状态支持,则同步删除数据库中对应的NextEntry数据(如果有的话) if self.koalaStatus: self.koalaStatus.remove_next_entry([entryURL]) # 如果即将达到最大深度,处于性能考虑,不再进入子页面 if maxDepth - 1 <= 0: return else: # 准备进入子页面之前,同步更新状态 if self.koalaStatus: self.koalaStatus.add_next_entry(nextEntries) # 广度优先抓取 for nextEntryURL in nextEntries: if Common.hash(nextEntryURL) not in self.visitedEntriesHash: for i in self.__crawl_proc(nextEntryURL, maxDepth - 1): yield i