前言
這篇內(nèi)容講解js生成內(nèi)容型,上一篇講解的所需內(nèi)容是全部在html源碼中可以找得到的,而比如攜程網(wǎng)隨便打開一個旅店的頁面,像圖1酒店點評下面的位置等信息在開發(fā)者工具中的html代碼中可看到在類為bar_score的div中,可是查看源代碼搜索bar_score卻并沒有搜索結(jié)果,原因是因為這部分內(nèi)容是由js代碼生成,然后在頁面渲染時填充進div中,故源代碼中是找不到的。
解決方式:Seleunium+BeautifulSoup
1.Selenium介紹####
Selenium本來是作為一個自動化測試工具。它可以模擬瀏覽器行為,比如打開瀏覽器,點擊某個元素等等。所以可以利用它獲取頁面加載后的元素
安裝
大多數(shù)的python第三方庫都可以在以下網(wǎng)站中搜索找到,[python packageIndex]。后面提到的第三方庫均可在該網(wǎng)站中下載。(https://pypi.python.org/)
Selenuim僅支持部分瀏覽器,這里以火狐瀏覽器為例,并且需要最好下載配套的Selenium和fiirefox,否則可能會遇到版本不兼容問題。這里所用到的是selenium2.42.1和firefox27.0.1(好老)。
操作
打開一個瀏覽器和網(wǎng)址
from selenium import webdriver
driver = webdriver.Firefox() #打開火狐瀏覽器
url = 'https://www.baidu.com'
driver.set_page_load_timeout(20) #設(shè)置頁面超時時間
driver.get(url) #打開對應(yīng)網(wǎng)址
常用方式定位元素
等待
Selenium中的等待,有時候頁面加載需要一定時間,如果立即定位元素可能會出現(xiàn)獲取不到元素的錯誤,所以需要設(shè)置等待。
1.強制等待sleep(xx),強制讓閃電俠等xx時間,不管瀏覽器能不能跟上速度,還是已經(jīng)提前到了,都必須等xx秒。
sleep(3)
2.隱形等待是設(shè)置了一個最長等待時間,如果在規(guī)定時間內(nèi)網(wǎng)頁加載完成,則執(zhí)行下一步,否則一直等到時間截止,然后執(zhí)行下一步。隱性等待,最長等30秒
driver.implicitly_wait(30)
3.顯性等待,WebDriverWait,配合該類的until()和until_not()方法,就能夠根據(jù)判斷條件而進行靈活地等待了。它主要的意思就是:程序每隔xx秒看眼,如果條件成立了,則執(zhí)行下一步,否則繼續(xù)等待,直到超過設(shè)置的最長時間,然后拋出TimeoutException。隱性等待和顯性等待可以同時用,但要注意:等待的最長時間取兩者之中的大者
from selenium.webdriver.support.wait import WebDriverWait
WebDriverWait(driver, 20, 0.5).until(EC.presence_of_element_located(locator))
2.BeautifulSoup####
Beautiful Soup是一個可以從HTML或XML文件中提取數(shù)據(jù)的Python庫,Beautiful Soup 3 目前已經(jīng)停止開發(fā),推薦在現(xiàn)在的項目中使用Beautiful Soup 4,不過它已經(jīng)被移植到BS4了,也就是說導(dǎo)入時我們需要 import bs4 。
from bs4 import BeautifulSoup
可以通過id、css類、文檔樹等多種方式提取數(shù)據(jù),具體使用查看官方文檔:https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html
舉例:
from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc,"html.parser")
comment_soup = index.findAll("div", { "class" : "v2_reviewsitem" })
3.xlwt、xlrd、xlutils進行excel讀寫操作
xlwt::對excel進行寫操作
基本操作如下,詳情可參考以下文檔:http://xlwt.readthedocs.io/en/latest/genindex.html
file = xlwt.Workbook() #新建一個工作簿
sheet1 = file.add_sheet(u'sheet1',cell_overwrite_ok=True) #新建一個工作表,并設(shè)置可被改寫
sheet1.col(0).width=256*22 #設(shè)置第一列的列寬,xlwt的列寬是以256為一個單位
style = xlwt.easyxf('align: wrap on, vert centre, horiz center') #easyxf設(shè)置表格樣式,此處設(shè)置align為垂直水平居中
sheet1.write(0,1,‘text',style) #向第1行第2列以style樣式寫入text字符串
xlrd:對excel進行讀操作
rb_file = xlrd.open_workbook('D:/score.xls') #打開文件
# 獲取指定sheet的方式
sheet1 = rb_file.get_sheet(0)
sheet2 = rb_file.sheet_by_index(1) # sheet索引從0開始
sheet3 = rb_file.sheet_by_name('sheet2')
# sheet的姓名、行列操作
print sheet2.name,sheet2.nrows,sheet2.ncols
rows = sheet2.row_values(3) # 獲取第四行內(nèi)容
cols = sheet2.col_values(2) # 獲取第三列內(nèi)容
#單元格操作
print sheet2.cell(1,0).value.encode('utf-8')
print sheet2.cell_value(1,0).encode('utf-8')
print sheet2.row(1)[0].value.encode('utf-8')
print sheet2.cell(1,0).ctype
xlutils:對excel進行追加修改操作
追加方法如下,通過對原有文件建立一個臨時副本,在副本后末尾寫入新數(shù)據(jù),再將原有文件覆蓋
rb_file = xlrd.open_workbook('D:/score.xls')
nrows = rb_file.sheets()[0].nrows #獲取已存在的excel表格行數(shù)
wb_file = copy(rb_file) #復(fù)制該excel
sheet1 = wb_file.get_sheet(0) #建立該excel的第一個sheet副本,通過sheet_by_index()獲取的sheet沒有write()方法
try:
for i in range(0,len(score_list)):
for j in range(0,len(score_list[i])):
#將數(shù)據(jù)追加至已有行后
sheet1.write(i+nrows,j,score_list[i][j])
except Exception, e:
print e
wb_file.save('D:/score.xls')
攜程爬蟲實現(xiàn)##
首先進入酒店列表的頁面,此處是在首頁設(shè)置城市為武漢之后進來的頁面,在酒店列表獲取紅圈中的如下信息,查看詳情是獲取該按鈕對應(yīng)的鏈接地址,方便后面進入酒店的詳情頁面
然后是酒店詳情頁面的如下信息
最終獲取數(shù)據(jù)結(jié)果如下圖
程序的整體思路是,
1.以每一頁為一個單位,爬取一頁數(shù)據(jù)并存儲完畢后,使用selenium模擬點擊下一頁。
2.每一頁使用selenuim獲取酒店列表的單條酒店信息的整體html,轉(zhuǎn)換成beautifulsoup所需格式存儲至hotel_list。
3.遍歷hotel_list,使用beautifulsoup提取圖一的信息,再跳轉(zhuǎn)至詳情頁面爬取圖二中的信息。將圖一圖二信息整合至hotel_info的元組,最后追加至info_list列表,構(gòu)成一頁所有酒店的所需信息。
4.每爬取一頁存儲至excel。因為使用selenium不斷打開瀏覽器操作多了之后會出現(xiàn)瀏覽器無響應(yīng)的情況,為避免最后沒有數(shù)據(jù),所有采用爬取一頁存一頁的方式。
代碼簡單講解:
需下載的庫,bs4,xlwt,xlrd,xlutils,selenium
程序結(jié)構(gòu)main 為程序入口,bulid_excel建立excel文件供存儲數(shù)據(jù),read_hotel爬取數(shù)據(jù),并設(shè)置每一頁爬取完畢后調(diào)用save_score向建立的excel追加寫入數(shù)據(jù)。
# -*- coding: utf-8 -*- #這行很重要,所有python代碼最好都以utf-8形式編碼,以防止中文亂碼
import urllib2 #和urlib類似,但是可以構(gòu)造request請求
from bs4 import BeautifulSoup
import re
import os #文件操作庫
import xlwt
import xlrd
from xlutils.copy import copy
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time #時間操作庫,強制等待sleep需引入
import traceback #一個很好的錯誤追蹤
import socket
#driver.set_page_load_timeout(30)
socket.setdefaulttimeout(30)
class CommentContent:
#讀取武漢酒店列表
def read_hotel(self):
driver = webdriver.Firefox() #打開瀏覽器
driver.maximize_window() #最大化窗口
page = 0 #提示作用
index_url = 'http://hotels.ctrip.com/hotel/wuhan477#ctm_ref=ctr_hp_sb_lst'
driver.get(index_url) #打開待爬取酒店列表頁面
#模擬瀏覽器勾選酒店類型為青年旅社
btn_qn = driver.find_element_by_id('feature-35')
btn_qn.click()
time.sleep(5)
info_list = []
hotel_list = [] #存儲所有帶爬取酒店的信息
#range()中的數(shù)字可指定需要爬取的頁數(shù)
for i in range(4):
hotel_loc = []
#獲取該頁的酒店列表
hotel_loc = driver.find_elements_by_xpath("http://div[@class='searchresult_list searchresult_list2']")
count = 0
page_count = len(hotel_loc)
print page_count
while count < page_count:
try:
hotel = ''
#獲取酒店列表的html,供beautiful轉(zhuǎn)換后方便提取
hotel = hotel_loc[count].get_attribute("innerHTML")
hotel_list.append(BeautifulSoup(hotel,"html.parser"))
count += 1
except Exception,e:
#print e
print 'get hotel html error'
#continue
#點擊下一頁按鈕
btn_next = driver.find_element_by_id('downHerf')
btn_next.click()
time.sleep(10)
count = 0
hotel_count = len(hotel_list)
#遍歷酒店列表里的每一條酒店信息
while count < hotel_count:
try:
#獲取每一條酒店的總體評分、用戶推薦%數(shù)、查看詳情的鏈接地址
hotel_name = hotel_list[count].findAll("h2",{"class","searchresult_name"})[0].contents[0]['title']
total_judgement_score = hotel_list[count].findAll("span", { "class" : "total_judgement_score" })[1].get_text()
hotel_judgement = hotel_list[count].find("span", { "class" : "hotel_judgement" }).get_text()
detail_href = hotel_list[count].find("a", { "class" : "btn_buy" })['href']
#構(gòu)造酒店詳情信息的url
detail_url = 'http://hotels.ctrip.com/' + detail_href
try:
#進入酒店詳情頁面
print '1-------------'
driver.get(detail_url)
print "start new page"
except TimeoutException:
print 'time out after 30 seconds when loading page'
time.sleep(3)
#點擊酒店點評
try:
WebDriverWait(driver, 5).until(lambda x: x.find_element_by_id("commentTab")).click()
#程序執(zhí)行到該處使用driver等待的方式并不能進入超時exception,也不能進入設(shè)定好的頁面加載超時錯誤?????
except socket.error:
print 'commtab error'
time.sleep(10)
#driver.execute("acceptAlert") #此行一直出錯,瀏覽器跳出警告框????無法執(zhí)行任何有關(guān)driver 的操作
#continue
#driver.quit()
try:
time.sleep(3)
bar_score = WebDriverWait(driver, 10).until(lambda x: x.find_element_by_xpath("http://div[@class='bar_score']"))
except Exception, e:
print 'bbbbbbbbbbbb'
print e
#bar_score = driver.find_element_by_xpath("http://div[@class='bar_score']")
#對獲取內(nèi)容進行具體的正則提取
total_score_ptn = re.compile('(.*?)%')
try:
total_score = total_score_ptn.findall(total_judgement_score)[0]
#total_score = total_score_ptn.search(total_judgement_score).group(1)
hotel_sale_ptn = re.compile(r'\d+')
#hotel_sale = hotel_sale_ptnsearch(hotel_judgement).group(1)
hotel_sale = hotel_sale_ptn.findall(hotel_judgement)[0]
except Exception, e:
print 'tote error'
print e
#獲取位置、設(shè)施、服務(wù)、衛(wèi)生評分
bar_scores_ptn = re.compile(r'\d.\d') #提取字符串中的數(shù)字,格式類似于3.4
bar_scores = bar_scores_ptn.findall(bar_score.text)
try:
loc_score = bar_scores[0]
device_score = bar_scores[1]
service_score = bar_scores[2]
clean_score = bar_scores[3]
except Exception, e:
print '0------'
print e
continue
#將每個酒店的所有數(shù)據(jù)以元祖形式存儲進hotel_info,存儲成元組是為了方便后面寫入excel,
#后將所有酒店信息追加至info_list
hotel_info = (hotel_name,total_score,hotel_sale,loc_score,device_score,service_score,clean_score)
info_list.append(hotel_info)
count += 1
#每一頁有25個酒店,每爬取一頁顯示next page提示,并調(diào)用save_score方法存儲進excel.
#另外重啟瀏覽器,以防止其崩潰
if count % 24 == 0:
print "next page"
CommentContent().save_score(info_list)
info_list = []
driver.close()
time.sleep(10)
driver = webdriver.Firefox()
except Exception, e:
print 'get detail info error'
#print e
count += 1
continue
#traceback.print_exc()
#driver.close()
break
return info_list
#建立數(shù)據(jù)存儲的excel和格式,以及寫入第一行
def build_excel(self):
file = xlwt.Workbook()
sheet1 = file.add_sheet(u'sheet1',cell_overwrite_ok=True)
head_style = xlwt.easyxf('font: name Times New Roman, color-index red, bold on',
num_format_str='#,##0.00')
row0 = ('hotel_name','total_score','sale','loc_score','device_score', 'service_score', 'clean_score')
for i in range(0,len(row0)):
sheet1.write(0,i,row0[i],head_style)
file.save('D:/score1.xls')
#將數(shù)據(jù)追加至已建立的excel文件中
def save_score(self, info_list):
score_list = info_list
rb_file = xlrd.open_workbook('D:/score.xls')
nrows = rb_file.sheets()[0].nrows #獲取已存在的excel表格行數(shù)
wb_file = copy(rb_file) #復(fù)制該excel
sheet1 = wb_file.get_sheet(0) #建立該excel的第一個sheet副本
try:
for i in range(0,len(score_list)):
for j in range(0,len(score_list[i])):
#將數(shù)據(jù)追加至已有行后
sheet1.write(i+nrows,j,score_list[i][j])
except Exception, e:
print e
wb_file.save('D:/score1.xls')
print 'save success'
#程序入口
if __name__ == '__main__':
CommentContent().build_excel()
CommentContent().read_hotel()