上一篇文章中,我们只实现了爬虫,爬取网站的源代码,但大多数情况下是我们需要爬取网站的感兴趣的内容。 通过跟踪所有的连接方式,我们可以很容易地下载到整个网站的页面。但是,这种方法会下载大量我们并不需要的网页。例如,我们想要从一个在线论坛中抓取用户帐号的详情页,那么此时我们只需要下载帐号页,而不需要下载讨论帖的页面。下面将使用正则表达式来确定需要下载哪些页面。下面是这段代码的初始版本。
import re def link_crawler(seed_url,link_regex): """ Crawl from the given seed URL following links matched by link_regex :param seed_url: :param link_regex: :return: """ crawl_queue = [seed_url] while crawl_queue: url = crawl_queue.pop() html = download(url) # filter for links matching our regular expression for link in get_links(html): if re.match(link_regex,link): crawl_queue.append(link) def get_links(html): """ return a list of links from html :param html: :return: """ # a regular expression to extract all links from the webpage webpage_regex = re.compile('<a[^>]+href=["\'](.*?)["\']',re.IGNORECASE) # list of all links from the webpage return webpage_regex.findall(html) 要运行这段代码,只需要调用link_crawler函数,并传入两个参数:要爬取的网站的URL和用于跟踪连接的正则表达式。对于示例网站,我们想要爬取的是国家列表索引页和国家页面。其中,索引页连接格式如下: http://example.webscraping.com/index/1 http://example.webscraping.com/index/2 国家页链接格式: http://example.webscraping.com/view/Afghanistan-1 http://example.webscraping.com/view/Aland-Islands-2 因此我们可以用/(index|view)/这个简单的正则表达式来匹配这两类网页。当爬虫适用这些输入参数运行时会发生什么呢?你会发现下面的错误。 ValueError:unknown url type:/index/1 可以看出,问题出在下载/index/1时,该链接只有网页的路径部分,而没有协议和服务器部分,也就是说这是一个相对链接。由于浏览器正在浏览哪个网页,所以在浏览器浏览时,相对链接是能够正常工作的。但是,urllib2是无法获知上下文的。为了让urllib2能够定位网页,我们需要将链接转换为绝对连接的形式,以便包含定位网页的所有细节。Python中确实有用来实现这一功能的模块,该模块称为urlparse。下面是link_crawler的改进版本,使用了urlparse模块来创建绝对路径。 import urlparse def link_crawler(seed_url,link_regex): """ Crawl from the given seed URL following links matched by link_regex :param seed_url: :param link_regex: :return: """ crawl_queue = [seed_url] while crawl_queue: url = crawl_queue.pop() html = download(url) # filter for links matching our regular expression for link in get_links(html): if re.match(link_regex,link): link = urlparse.urljoin(seed_url,link) crawl_queue.append(link)当运行这段代码时,会发现虽然网页下载没有出现错误,但是同样的地点总是会不断下载到。这是因为这些地点相互之间存在链接。比如澳大利亚链接到了南极洲,而南极洲也存在到澳大利亚的链接,此时爬虫就会在他们之间不断循环下去。要想避免重复爬取相同的链接,我们需要积累哪些链接已经被爬取过。下面是修改后的Link_crawler函数,已具备存储已发现URL的功能,可以避免重复下载。
import urlparse def link_crawler(seed_url,link_regex): """ Crawl from the given seed URL following links matched by link_regex :param seed_url: :param link_regex: :return: """ crawl_queue = [seed_url] seen = set(crawl_queue) while crawl_queue: url = crawl_queue.pop() html = download(url) # filter for links matching our regular expression for link in get_links(html): if re.match(link_regex,link): link = urlparse.urljoin(seed_url,link) if link not in seen: seen.add(link) crawl_queue.append(link 当运行该脚本时,它会爬取所有地点,并且能够如期停止。最终,我们得到了一个可用的爬虫。