http://www.cnblogs.com/binye-typing/p/6656595.html
读者可能会奇怪我标题怎么理成这个鬼样子,主要是单单写 lxml 与 bs4 这两个 py 模块名可能并不能一下引起大众的注意,一般讲到网页解析技术,提到的关键词更多的是 BeautifulSoup 和 xpath ,而它们各自所在的模块(python 中是叫做模块,但其他平台下更多地是称作库),很少被拿到明面上来谈论。下面我将从效率、复杂度等多个角度来对比 xpath 与 beautifulsoup 的区别。
效率
使用复杂度
1 def get_nav(self,response): 2 # soup = BeautifulSoup(response.body_as_unicode(), 'lxml') 3 # nav_list = soup.find('ul', id='nav').find_all('li') 4 model = etree.HTML(response.body_as_unicode()) 5 nav_list = model.xpath('//ul[@id="nav"]/li') 6 for nav in nav_list[1:]: 7 # href = nav.find('a').get('href') 8 href = nav.xpath('./a/@href')[0] 9 yield Request(href, callback=self.get_url)
可以看到 xpath 除了其特殊的语法看上去有些别扭(跟正则表达式似的)以外,它在代码简洁度上还是可观的,只是所有 xpath 方法的返回结果都是一个 list ,如果匹配目标是单个元素,对于无脑下标取0的操作,强迫症患者可能有些难受。相比之下,BeautifulSoup 这一长串的 find 与 find_all 方法显得有些呆板,如果碰到搜索路线比较曲折的,比如:
# href = article.find('div', class_='txt').find('p', class_='tit blue').find('span').find('em').find('a').get('href') href = article.xpath('./div[@class="txt"]//p[@class="tit blue"]/span/em/a/@href')[0]
这种情况下,BeautifulSoup 的写法就显得有些让人反胃了,当然一般情况下不会出现这么长的路径定位。
功能缺陷总结——BeautifulSoup
BeautifulSoup 在使用上的一个短板,就是在嵌套列表中去匹配元素的时候会显得很无力,下面是一个例子(具体网页结构可根据 index_page 在浏览器打开进行审查):
1 class RankSpider(spider): 2 name = 'PCauto_rank' 3 index_page = 'http://price.pcauto.com.cn/top/hot/s1-t1.html' 4 api_url = 'http://price.pcauto.com.cn%s' 5 6 def start_requests(self): 7 yield Request(self.index_page, callback=self.get_left_nav) 8 9 # 测试 BeautifulSoup 是否能连续使用两个 find_all 方法 10 def get_left_nav(self,response): 11 # model = etree.HTML(response.body_as_unicode()) 12 # nav_list = model.xpath('//div[@id="leftNav"]/ul[@class="pb200"]/li//a[@class="dd "]') 13 soup = BeautifulSoup(response.body_as_unicode(), 'lxml') 14 nav_list = soup.find('div', id='leftNav').find('ul', class_='pb200').find_all('li').find_all('a', class_='dd') 15 for sub_nav in nav_list: 16 href = self.api_url % sub_nav.xpath('./@href')[0] 17 yield Request(href, callback=self.get_url) 18 19 def get_url(self): 20 pass
使用注释部分的 xpath 写法没什么问题,可实现准确定位,但用到 BeautifulSoup 去实现相应逻辑的时候,就要连续使用两个 find_all 方法 ,显然这种写法不符合规范,运行的时候会报 AttributeError: 'ResultSet' object has no attribute 'find_all' 错误,这时候我们要实现这种匹配,只能先去遍历各个 li ,然后调 find_all 方法找到 li 下的各个 a 标签,实在繁琐,所以这种场景用 xpath 来解决会省下不少麻烦。
当然这里我只是单单为了诠释这么个问题才在故意在拿目标 url 时分这么多级的,实际开发中我这里用的是:
1 # nav_list = model.xpath('//div[@id="leftNav"]///a[@class="dd "]') 2 nav_list = soup.find('div', id='leftNav').find_all('a', class_='dd')
但如果说我们的目标不是所有的 li 下面的 a 标签,而是部分 class="*" 的 li 下面的 a 标签,这时候我们就只能选择使用 xpath 来达到目的,当然如果你喜欢写遍历,觉得这样写出来逻辑展示更清晰,那你可以跳过这一节。
功能缺陷总结——xpath
1 model = etree.HTML(response.body_as_unicode()) 2 model.xpath('//div[@class="box box-2 box-4"]')
model.xpath('//div[@class="box box-2 box-4 mt25"]') model.xpath('//div[@class="box box-2 box-4 mt17"]')
来匹配目标,这可能要归结于 xpath 在设计的时候本身就是以类名的完全匹配来确定目标的,哪怕多一个空格:
model.xpath('//a[@class="dd"]')
文本获取
xpath 目标结点的 text 属性值对应的只是当前匹配元素下面的文本信息,如要获取该结点下面包括子结点在内的所有文本内容要使用 .xpath('string()') 的方式:
1 model = etree.HTML(response.body_as_unicode()) 2 place = model.xpath('//div[@class="guide"]') 3 # nav and aiticle 4 if place: 5 mark = place[0].xpath('./span[@class="mark"]') 6 if mark: 7 # text = mark[0].text.strip().replace('\n','').replace('\r','') # false 8 text = mark[0].xpath('string()') 9 result['address'] = text