上一篇文章中,我们只实现了爬虫,爬取网站的源代码,但大多数情况下是我们需要爬取网站的感兴趣的内容。
通过跟踪所有的连接方式,我们可以很容易地下载到整个网站的页面。但是,这种方法会下载大量我们并不需要的网页。例如,我们想要从一个在线论坛中抓取用户帐号的详情页,那么此时我们只需要下载帐号页,而不需要下载讨论帖的页面。下面将使用正则表达式来确定需要下载哪些页面。下面是这段代码的初始版本。
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(']+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
当运行该脚本时,它会爬取所有地点,并且能够如期停止。最终,我们得到了一个可用的爬虫。