前一段接到任务,要爬 36Kr 网站首页的新闻,当时想的时应该很简单吧,跟之前爬 “不得姐” 和 “糗百” 应该差不多,可是实际操作时还是遇到了几个问题的。下面把自己的爬取方法分享出来,可能有比这更好的方法,欢迎交流。
一、分析网页代码
我们需要爬取的内容在网页中的位置如下,“最新文章” 下面的新闻。
打开调试模式,看看代码,发现它的外层是一个 <ul class="feed_ul"> 标签,我们需要获取的信息,在其内部的 <li> 标签中。
打开一个 <li> 标签,我们看到要获取的新闻标题、图片、链接位置如下。
当时想,还是一样的套路,拿到这些标签对应的元素不就行了,图样图森破……
二、问题及解决方法
找到了要获取的信息位置,开始写代码。
#!/usr/bin/env python
#coding:utf-8
from selenium import webdriver
class Kr:
def __init__(self):
self.dr = webdriver.Chrome()
self.dr.get('http://36kr.com/')
def loadData(self):
feed_ul = self.dr.find_element_by_class_name('feed_ul')
# 写到这卡住了
self.quit()
def quit(self):
self.dr.quit()
Kr().loadData()
获取到 feed_ul 后不会写了,因为里面的 li 标签没有 class 属性,这怎么弄?
查了一下, Selenium 还有一个定位元素的方法 find_elements_by_tag_name,通过标签名来获取元素,于是写下了下面的代码。
def loadData(self):
feed_ul = self.dr.find_element_by_class_name('feed_ul')
li = feed_ul.find_elements_by_tag_name('li')
for i in li:
img_box = i.find_element_by_class_name('img_box')
print img_box
# load-img fade 获取不到
img = img_box.find_element_by_class_name('load-img fade')
print img
break
self.quit()
因为调试阶段我只让它遍历一次就 break 了,方便测试。img_box 可以成功获取,而这个 class 为 load-img fade 的 img 标签却获取不到,程序会报错。再看看网页代码,发现 img 外层还有一个无 class 的 div,会不会是这个原因。
然后又查了一下,发现了上篇文章中提到的 find_element_by_xpath,通过标签的位置来定位元素,可以理解为标签在网页中的路径。于是又加上了下面的代码。
img = img_box.find_element_by_xpath('//div/img')
print img.get_attribute('src')
'//div/img' 表示在 img_box 中第一个 div 中的第一个 img 标签。
虽然这样获取到了 img 标签,也打印出了它的 src,但是竟然是这样一个图片。
发现不对劲,但是还不知道哪里出错了😂 ,既然这样我干脆直接拿 xpath 来定位 img。
for i in li:
xpath = "//div[@class='am-cf inner_li inner_li_abtest']/a/div[@class='img_box']/div/img"
img = i.find_element_by_xpath(xpath)
print img.get_attribute('src')
break
可能你会惊叹,这么一长串的 xpath 怎么写的,一个一个找么?当然不是,Chrome 有一个插件,可以获取网页元素的 xpath,这个我们最后再说,先来看问题。
这样写能够获取 img,但是打印出的 src 竟然是个 None,当时也是很迷糊。后来想可能是网页通过 Ajax 加载的列表,因为网速较慢,所以当时 img 还没有获取到 src,所以是个 None。为了确认自己没写错,我打印了一下 alt。
print img.get_attribute('alt')
可以获取新闻的标题,没错啊?感觉自己写的是假代码。然后我把 break 去掉,又运行了一次代码。
- 卧槽!什么情况?怎么都是一样的?又哪里出问题了?
当时我就这样,一脸懵逼。经历了这个打击后,换了个路子,我不去拿所有的 li 标签了,拿到 feed_ul 后直接通过 xpath 定位元素。可以看到 36kr 首页一共有 28个 li 标签,其中属于新闻的只有 20 个,其他的是话题或者是空的。改写了如下代码。
def loadData(self):
feed_ul = self.dr.find_element_by_class_name('feed_ul')
i = 1
while i <= 28:
try:
xpath_head = "//li[" + str(i) + "]/div[@class='am-cf inner_li inner_li_abtest']/a/div[@class='intro']/h3"
xpath_href = "//li[" + str(i) + "]/div[@class='am-cf inner_li inner_li_abtest']/a/div[@class='img_box']/div"
xpath_img = "//li[" + str(i) + "]/div[@class='am-cf inner_li inner_li_abtest']/a/div[@class='img_box']/div/img"
head = feed_ul.find_element_by_xpath(xpath_head)
title = head.text
href = feed_ul.find_element_by_xpath(xpath_href)
url = 'http://36kr.com' + href.get_attribute('href')
img = feed_ul.find_element_by_xpath(xpath_img)
src = img.get_attribute('src')
except Exception as e:
print 'error'
else:
print title
print url
print src
i += 1
self.quit()
别说,还真好了,不再是同样的标题,只是除了第一个新闻能获取到 src,其他的都是 None。
又有点小懵逼了,网速慢?不会啊,下电影还 500kb/s 多呢!然后又开始各种胡乱试,一度快要放弃,突然发现了下图的情况。
只有处于当前窗口内的图片会显示,下面的图片还是灰的,滚动到窗口内才会被加载。
- 我是个天才,这都被我发现了!
当时就是这个想法,发现了问题还是很开心,不要喷我太水😂 ,毕竟第一次遇到这个问题。既然图片不在当前窗口内,那我就让浏览器滚一滚呗。
img = feed_ul.find_element_by_xpath(xpath_img)
src = img.get_attribute('src')
t = 1
while t <= 3:
if src == None:
self.dr.execute_script('window.scrollBy(0,200)')
time.sleep(3)
src = img.get_attribute('src')
t += 1
else:
break
判断一下当前的 src 是否为 None,空的话就向下滚 200px(这个根据你爬取的网页自己设置),怕网速不给力,再睡 3 秒,重新获取一下。因为列表中存在专题栏目,避免向下滚动 200 正好处于专题位置还是拿不到 src 我又循环了 3 次。为什么是 3 ?事不过三😄
- 关于 Selenium 对应浏览器的操作,以下几个也是可能会用到的
dr.set_window_size(w, h) # 设置浏览器宽高
dr.execute_script('window.scrollBy(x, y)') # 控制浏览器滚动 x 向右 y 向下(相对于当前位置)
dr.execute_script('window.scrollTo(x, y)') # 控制浏览器滚动 x、y 为左上角坐标(相对于浏览器)
dr.refresh() # 刷新页面
三、完整代码与演示
#!/usr/bin/env python
#coding:utf-8
from selenium import webdriver
import time
class Kr:
def __init__(self):
self.dr = webdriver.Chrome()
self.dr.get('http://36kr.com/')
def loadData(self):
feed_ul = self.dr.find_element_by_class_name('feed_ul')
i = 1
while i <= 28:
try:
xpath_head = "//li[" + str(i) + "]/div[@class='am-cf inner_li inner_li_abtest']/a/div[@class='intro']/h3"
xpath_href = "//li[" + str(i) + "]/div[@class='am-cf inner_li inner_li_abtest']/a/div[@class='img_box']/div"
xpath_img = "//li[" + str(i) + "]/div[@class='am-cf inner_li inner_li_abtest']/a/div[@class='img_box']/div/img"
head = feed_ul.find_element_by_xpath(xpath_head)
title = head.text
href = feed_ul.find_element_by_xpath(xpath_href)
url = 'http://36kr.com' + href.get_attribute('href')
img = feed_ul.find_element_by_xpath(xpath_img)
src = img.get_attribute('src')
t = 1
while t <= 3:
if src == None:
self.dr.execute_script('window.scrollBy(0,200)')
time.sleep(3)
src = img.get_attribute('src')
t += 1
else:
break
except Exception as e:
print 'error\n'
else:
self.saveData(title, url, src)
i += 1
self.quit()
def saveData(self, title, url, src):
# 这里拿到数据可以存入数据库或其他操作
print title, '\n', url, '\n', src, '\n'
def quit(self):
self.dr.quit()
while 1:
Kr().loadData()
time.sleep(600)
欧了,因为是爬取新闻,也不涉及翻页的问题了,每隔一段时间调用一次 loadData() 即可。
对了,关于那个获取 xpath 的插件,可以在 Chrome 的商店中搜索 XPath Helper 下载安装即可。使用也非常简单,shift + command + x 可以开启,若无反响刷新下页面或重启浏览器,按住 shift,移动鼠标到想获取 xpath 的元素上即可。