前一段接到任務(wù),要爬 36Kr 網(wǎng)站首頁的新聞,當(dāng)時(shí)想的時(shí)應(yīng)該很簡(jiǎn)單吧,跟之前爬 “不得姐” 和 “糗百” 應(yīng)該差不多,可是實(shí)際操作時(shí)還是遇到了幾個(gè)問題的。下面把自己的爬取方法分享出來,可能有比這更好的方法,歡迎交流。
一、分析網(wǎng)頁代碼
我們需要爬取的內(nèi)容在網(wǎng)頁中的位置如下,“最新文章” 下面的新聞。

打開調(diào)試模式,看看代碼,發(fā)現(xiàn)它的外層是一個(gè) <ul class="feed_ul"> 標(biāo)簽,我們需要獲取的信息,在其內(nèi)部的 <li> 標(biāo)簽中。

打開一個(gè) <li> 標(biāo)簽,我們看到要獲取的新聞標(biāo)題、圖片、鏈接位置如下。

當(dāng)時(shí)想,還是一樣的套路,拿到這些標(biāo)簽對(duì)應(yīng)的元素不就行了,圖樣圖森破……
二、問題及解決方法
找到了要獲取的信息位置,開始寫代碼。
#!/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 后不會(huì)寫了,因?yàn)槔锩娴?li 標(biāo)簽沒有 class 屬性,這怎么弄?
查了一下, Selenium 還有一個(gè)定位元素的方法 find_elements_by_tag_name,通過標(biāo)簽名來獲取元素,于是寫下了下面的代碼。
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()
因?yàn)檎{(diào)試階段我只讓它遍歷一次就 break 了,方便測(cè)試。img_box 可以成功獲取,而這個(gè) class 為 load-img fade 的 img 標(biāo)簽卻獲取不到,程序會(huì)報(bào)錯(cuò)。再看看網(wǎng)頁代碼,發(fā)現(xiàn) img 外層還有一個(gè)無 class 的 div,會(huì)不會(huì)是這個(gè)原因。

然后又查了一下,發(fā)現(xiàn)了上篇文章中提到的 find_element_by_xpath,通過標(biāo)簽的位置來定位元素,可以理解為標(biāo)簽在網(wǎng)頁中的路徑。于是又加上了下面的代碼。
img = img_box.find_element_by_xpath('//div/img')
print img.get_attribute('src')
'//div/img' 表示在 img_box 中第一個(gè) div 中的第一個(gè) img 標(biāo)簽。
雖然這樣獲取到了 img 標(biāo)簽,也打印出了它的 src,但是竟然是這樣一個(gè)圖片。
發(fā)現(xiàn)不對(duì)勁,但是還不知道哪里出錯(cuò)了?? ,既然這樣我干脆直接拿 xpath 來定位 img。
for i in li:
xpath = "http://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
可能你會(huì)驚嘆,這么一長(zhǎng)串的 xpath 怎么寫的,一個(gè)一個(gè)找么?當(dāng)然不是,Chrome 有一個(gè)插件,可以獲取網(wǎng)頁元素的 xpath,這個(gè)我們最后再說,先來看問題。
這樣寫能夠獲取 img,但是打印出的 src 竟然是個(gè) None,當(dāng)時(shí)也是很迷糊。后來想可能是網(wǎng)頁通過 Ajax 加載的列表,因?yàn)榫W(wǎng)速較慢,所以當(dāng)時(shí) img 還沒有獲取到 src,所以是個(gè) None。為了確認(rèn)自己沒寫錯(cuò),我打印了一下 alt。
print img.get_attribute('alt')
可以獲取新聞的標(biāo)題,沒錯(cuò)???感覺自己寫的是假代碼。然后我把 break 去掉,又運(yùn)行了一次代碼。

- 臥槽!什么情況?怎么都是一樣的?又哪里出問題了?
當(dāng)時(shí)我就這樣,一臉懵逼。經(jīng)歷了這個(gè)打擊后,換了個(gè)路子,我不去拿所有的 li 標(biāo)簽了,拿到 feed_ul 后直接通過 xpath 定位元素??梢钥吹?36kr 首頁一共有 28個(gè) li 標(biāo)簽,其中屬于新聞的只有 20 個(gè),其他的是話題或者是空的。改寫了如下代碼。
def loadData(self):
feed_ul = self.dr.find_element_by_class_name('feed_ul')
i = 1
while i <= 28:
try:
xpath_head = "http://li[" + str(i) + "]/div[@class='am-cf inner_li inner_li_abtest']/a/div[@class='intro']/h3"
xpath_href = "http://li[" + str(i) + "]/div[@class='am-cf inner_li inner_li_abtest']/a/div[@class='img_box']/div"
xpath_img = "http://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()
別說,還真好了,不再是同樣的標(biāo)題,只是除了第一個(gè)新聞能獲取到 src,其他的都是 None。

又有點(diǎn)小懵逼了,網(wǎng)速慢?不會(huì)啊,下電影還 500kb/s 多呢!然后又開始各種胡亂試,一度快要放棄,突然發(fā)現(xiàn)了下圖的情況。

只有處于當(dāng)前窗口內(nèi)的圖片會(huì)顯示,下面的圖片還是灰的,滾動(dòng)到窗口內(nèi)才會(huì)被加載。
- 我是個(gè)天才,這都被我發(fā)現(xiàn)了!
當(dāng)時(shí)就是這個(gè)想法,發(fā)現(xiàn)了問題還是很開心,不要噴我太水?? ,畢竟第一次遇到這個(gè)問題。既然圖片不在當(dāng)前窗口內(nèi),那我就讓瀏覽器滾一滾唄。
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
判斷一下當(dāng)前的 src 是否為 None,空的話就向下滾 200px(這個(gè)根據(jù)你爬取的網(wǎng)頁自己設(shè)置),怕網(wǎng)速不給力,再睡 3 秒,重新獲取一下。因?yàn)榱斜碇写嬖趯n}欄目,避免向下滾動(dòng) 200 正好處于專題位置還是拿不到 src 我又循環(huán)了 3 次。為什么是 3 ?事不過三??
- 關(guān)于 Selenium 對(duì)應(yīng)瀏覽器的操作,以下幾個(gè)也是可能會(huì)用到的
dr.set_window_size(w, h) # 設(shè)置瀏覽器寬高
dr.execute_script('window.scrollBy(x, y)') # 控制瀏覽器滾動(dòng) x 向右 y 向下(相對(duì)于當(dāng)前位置)
dr.execute_script('window.scrollTo(x, y)') # 控制瀏覽器滾動(dòng) x、y 為左上角坐標(biāo)(相對(duì)于瀏覽器)
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 = "http://li[" + str(i) + "]/div[@class='am-cf inner_li inner_li_abtest']/a/div[@class='intro']/h3"
xpath_href = "http://li[" + str(i) + "]/div[@class='am-cf inner_li inner_li_abtest']/a/div[@class='img_box']/div"
xpath_img = "http://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):
# 這里拿到數(shù)據(jù)可以存入數(shù)據(jù)庫或其他操作
print title, '\n', url, '\n', src, '\n'
def quit(self):
self.dr.quit()
while 1:
Kr().loadData()
time.sleep(600)

歐了,因?yàn)槭桥廊⌒侣劊膊簧婕胺摰膯栴}了,每隔一段時(shí)間調(diào)用一次 loadData() 即可。
對(duì)了,關(guān)于那個(gè)獲取 xpath 的插件,可以在 Chrome 的商店中搜索 XPath Helper 下載安裝即可。使用也非常簡(jiǎn)單,shift + command + x 可以開啟,若無反響刷新下頁面或重啟瀏覽器,按住 shift,移動(dòng)鼠標(biāo)到想獲取 xpath 的元素上即可。
