前一段接到任務(wù),要爬 36Kr 網(wǎng)站首頁的新聞奥吩,當(dāng)時(shí)想的時(shí)應(yīng)該很簡單吧,跟之前爬 “不得姐” 和 “糗百” 應(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ì)驚嘆扳躬,這么一長串的 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 下載安裝即可。使用也非常簡單菠劝,shift + command + x 可以開啟赊舶,若無反響刷新下頁面或重啟瀏覽器,按住 shift赶诊,移動(dòng)鼠標(biāo)到想獲取 xpath 的元素上即可笼平。