用Python爬取實習(xí)信息(Scrapy初體驗)

原文出處: Cer_ml

1.目標(biāo)

這兩天要弄一個大作業(yè),從水木社區(qū)和北大未名社區(qū)的實習(xí)板塊,爬取實習(xí)信息,保存在MongoDB數(shù)據(jù)庫。
正好想學(xué)習(xí)一下scrapy框架的使用,就愉快地決定用scrapy來實現(xiàn)。

2.介紹

Scrapy是Python開發(fā)的一個快速,高層次的屏幕抓取和web抓取框架,用于抓取web站點并從頁面中提取結(jié)構(gòu)化的數(shù)據(jù)。使用了 Twisted 異步網(wǎng)絡(luò)庫來處理網(wǎng)絡(luò)通訊。整體架構(gòu):

學(xué)習(xí)使用Scrapy,最重要的是官方文檔。本文的主要參考資料也是該文檔。
Scrapy的安裝,這里就不說了,在滿足一系列依賴的安裝以后,pip一下,就搞定了。

pip install scrapy
3.開始
3.1 首先,新建一個Scrapy工程。

進(jìn)入你的目標(biāo)目錄,輸入以下指令,創(chuàng)建項目intern。

$ scrapy startproject intern

目錄結(jié)構(gòu)如下:

.
├── scrapy.cfg
└── intern
  ├── __init__.py
  ├── items.py
  ├── pipelines.py
  ├── settings.py
  └── spiders
    └── __init__.py

這個目錄結(jié)構(gòu)要熟記于心。

  • scrapy.cfg: 全局配置文件
  • intern/: 項目python模塊
  • intern/items.py: 項目items文件,定義爬取的數(shù)據(jù)保存結(jié)構(gòu)
  • intern/pipelines.py: 項目管道文件,對爬取來的數(shù)據(jù)進(jìn)行清洗、篩選、保存等操作
  • intern/settings.py: 項目配置文件
  • intern/spiders: 放置spider的目錄
3.2 編寫items.py文件。

定義item的字段如下:

import scrapy
class InternItem(scrapy.Item):
  title = scrapy.Field()
  href = scrapy.Field()
  author = scrapy.Field()
  time = scrapy.Field()
  content = scrapy.Field()
  is_dev = scrapy.Field()
  is_alg = scrapy.Field()
  is_fin = scrapy.Field()
  base_url_index = scrapy.Field()

定義的方法很簡單,每個字段都=scrapy.Field()即可。
使用:比如要使用某item的title,就像python中的dict一樣,item[‘title’]即可。

3.3 編寫爬蟲。

好了終于到了編寫爬蟲了。以爬取水木社區(qū)的爬蟲為例。在spiders目錄下,創(chuàng)建smSpider.py。

class SMSpider(scrapy.spiders.CrawlSpider):   
'''    
#要建立一個 Spider,你可以為 scrapy.spider.BaseSpider 創(chuàng)建一個子類,并確定三個主要的、強制的屬性:    
#name :爬蟲的識別名,它必須是唯一的,在不同的爬蟲中你必須定義不同的名字.    
#start_urls :爬蟲開始爬的一個 URL 列表。爬蟲從這里開始抓取數(shù)據(jù),所以,第一次下載的數(shù)據(jù)將會從這些 URLS 開始。其他子 URL 將會從這些起始 URL 中繼承性生成。   
#parse() :爬蟲的方法,調(diào)用時候傳入從每一個 URL 傳回的 Response 對象作為參數(shù),response 將會是 parse 方法的唯一的一個參數(shù),    
#這個方法負(fù)責(zé)解析返回的數(shù)據(jù)、匹配抓取的數(shù)據(jù)(解析為 item )并跟蹤更多的 URL。    
''' 
  name="sm"    
  base_url = 'http://www.newsmth.net/nForum/board/Intern'    
  start_urls = [base_url]   
  start_urls.extend([base_url+'?p='+str(i) for i in range(2,4)])    
  platform = getPlatform()    
  def __init__(self):        
    scrapy.spiders.Spider.__init__(self)        
    if self.platform == 'linux':            
      self.driver = webdriver.PhantomJS()        
    elif self.platform == 'win':            
      self.driver =webdriver.PhantomJS(executable_path= 'F:/runtime/python/phantomjs-2.1.1-windows/bin/phantomjs.exe')            
    self.driver.set_page_load_timeout(10)       
    dispatcher.connect(self.spider_closed, signals.spider_closed)    
  def spider_closed(self, spider):        
    self.driver.quit()    
  def parse(self,response):
...

從淺到深,一步步解釋這段代碼。
首先,這個SMSpider是繼承于CrawlSpider,CrawlSpider繼承于BaseSpider。一般用BaseSpider就夠了,CrawlSpider可以增加一些爬取的Rule。但實際上我這里并沒有用到。必需要定義的三個屬性。
name:爬蟲的名字。(唯一)
start_url:爬蟲開始爬取的url列表。
parse():爬蟲爬取的方法。調(diào)用時傳入一個response對象,作為訪問某鏈接的響應(yīng)。
在爬取水木社區(qū)的時候發(fā)現(xiàn),水木的實習(xí)信息是動態(tài)加載的。

也就是說,源代碼中,并沒有我們要的實習(xí)信息。這時,考慮使用Selenium和Phantomjs的配合。Selenium本來在自動化測試上廣泛使用,它可以模仿用戶在瀏覽器上的行為,比如點擊按鈕等等。Phantomjs是一個沒有UI的瀏覽器。Selenium和Phantomjs搭配,就可以方便地抓取動態(tài)加載的頁面。

回到SMSpider的代碼,我們要判斷當(dāng)前的操作系統(tǒng)平臺,然后在Selenium的webdriver中加載Phantomjs。Linux不用輸入路徑,Windows要輸入程序所在路徑。在init()的結(jié)尾,還要加上事件分發(fā)器,使得在爬蟲退出后,關(guān)閉Phantomjs。

self.driver.set_page_load_timeout(10)

這句代碼是為了不讓Phantom卡死在某一鏈接的請求上。設(shè)定每個頁面加載時間不能超過10秒。
具體的parse方法:

def parse(self,response):      
  self.driver.get(response.url)    
  print response.url
  #等待,直到table標(biāo)簽出現(xiàn)    
  try:        
    element = WebDriverWait(self.driver,30).until(  
               EC.presence_of_all_elements_located((By.TAG_NAME,'table'))        )        
    print 'element:\n', element    
  except Exception, e:        
    print Exception, ":", e        
    print "wait failed"    
  page_source = self.driver.page_source    
  bs_obj = BeautifulSoup(page_source, "lxml")    
  print bs_obj    
  table = bs_obj.find('table',class_='board-list tiz')    
  print table    
  print "find message ====================================\n" 
  intern_messages = table.find_all('tr',class_=False)    
  for message in intern_messages:        
    title, href, time, author = '','','',''        
    td_9 = message.find('td',class_='title_9')        
    if td_9:            
      title = td_9.a.get_text().encode('utf-8','ignore')            
      href = td_9.a['href']        
    td_10 = message.find('td', class_='title_10')        
    if td_10:            
      time=td_10.get_text().encode('utf-8','ignore')        
    td_12 = message.find('td', class_='title_12')        
    if td_12:            
      author = td_12.a.get_text().encode('utf-8','ignore')        
    item = InternItem()        
    print 'title:',title        
    print 'href:', href        
    print 'time:', time        
    print 'author:', author        
    item['title'] = title        
    item['href'] = href        
    item['time'] = time       
    item['author'] = author        
    item['base_url_index'] = 0        
    #嵌套爬取每條實習(xí)信息的具體內(nèi)容
    root_url = 'http://www.newsmth.net'              
    if href!='':            
    content = self.parse_content(root_url+href)            
    item['content'] = content       
    yield item

這段代碼,先是找到動態(tài)加載的目標(biāo)標(biāo)簽,等待這個標(biāo)簽出現(xiàn),再爬取實習(xí)信息列表,再嵌套爬取每條實習(xí)信息的具體內(nèi)容。這里我使用bs4對html進(jìn)行解析。你也可以使用原生態(tài)的Xpath,或者selector。這里就不進(jìn)行具體的講解了,多了解幾種方法,熟練一種即可。爬取到的目標(biāo)內(nèi)容,像 item[‘title’] = title這樣,保存在item里。注意最后不是return,而是yeild。parse方法采用生成器的模式,逐條爬取分析。
爬取具體實習(xí)內(nèi)容的代碼:

def parse_content(self,url):    
  self.driver.get(url)    
  try:        
    element = WebDriverWait(self.driver, 30).until(            
            EC.presence_of_all_elements_located((By.TAG_NAME, 'table'))        )        
    print 'element:\n', element    
  except Exception, e:        
    print Exception, ":", e        
    print "wait failed"    
  page_source = self.driver.page_source    
  bs_obj = BeautifulSoup(page_source, "lxml")    
  return bs_obj.find('td', class_='a-content').p.get_text().encode('utf-8','ignore’)

3.4 編寫pipelines.py。

接下來,我們想把爬取到的數(shù)據(jù),存在Mongodb里面。這可以交給pipeline去做。pipeline是我喜歡Scrapy的一個理由,你可以把你爬到的數(shù)據(jù),以item的形式,扔進(jìn)pipeline里面,進(jìn)行篩選、去重、存儲或者其他自定義的進(jìn)一步的處理。pipeline之間的順序,可以在settings.py中設(shè)置,這使得pipeline更加靈活。
來看看MongoDBPipeline:

class MongoDBPipeline(object):    
  def __init__(self):        
    pass     
  def open_spider(self, spider):        
    self.client = pymongo.MongoClient(            
    settings['MONGODB_SERVER'],            
    settings['MONGODB_PORT']        )        
    self.db = self.client[settings['MONGODB_DB']]        
    self.collection = self.db[settings['MONGODB_COLLECTION']]    
  def close_spider(self, spider):        
    self.client.close()    
  def process_item(self, item, spider):        
    valid = True        
    for data in item:            
      if not data :                
        valid = False                
        raise DropItem("Missing {0}!".format(data))        
      if item['title'] == '':            
        valid = False            
        raise DropItem("title is '' ")        
      if valid:            
        self.collection.insert(dict(item))            
    return item

來說明一下。
首先創(chuàng)建類MongoDBPipeline,這里不用繼承什么預(yù)先設(shè)定好的pipeline。但是要有一個process_item的方法,傳入一個item和spider,返回處理完的item。open_spider和close_spider是在爬蟲開啟和關(guān)閉的時候調(diào)用的回調(diào)函數(shù)。這里我們要用到MongoDB,所以我們在爬蟲開啟的時候,連接一個Mongo客戶端,在爬蟲關(guān)閉的時候,再把客戶端關(guān)掉。這里的數(shù)據(jù)庫相關(guān)的信息,都保存在settings.py里面。如下:

MONGODB_SERVER = "localhost"
MONGODB_PORT = 27017
MONGODB_DB = "intern"
MONGODB_COLLECTION = “items"

寫在settings.py里面的參數(shù)可以通過

from scrapy.conf import settings
settings['xxxxxx’]

這種方式來獲取。
在寫完MongoDBPipeline以后,還要在settings.py注冊一下這個pipeline,如下:

ITEM_PIPELINES = {    
    'intern.pipelines.TagPipeline': 100, 
    'intern.pipelines.MongoDBPipeline':300                  
}

后面的數(shù)值越小,越先執(zhí)行。數(shù)值的范圍是1000以內(nèi)的整數(shù)。通過這種方法,可以非常方便地設(shè)置pipeline之間的順序,以及開啟和關(guān)閉一個pipeline。

4.運行

在項目目錄下,執(zhí)行如下指令:

scrapy crawl sm

這時我們的SMSpider就愉快地開始爬取數(shù)據(jù)了。

5.下一步

關(guān)于scrapy框架,要學(xué)的還有很多。比如說擴(kuò)展和中間件的編寫,以及Crawler API的使用。
關(guān)于爬蟲,可以學(xué)習(xí)的還有:

  • 使用代理
  • 模擬登陸
    下面一段時間,要做新浪微博的爬蟲,屆時有新的收獲再和大家分享。
    本文源碼地址:github
    喜歡star一下哦~~~~

歡迎報名“第九屆移動互聯(lián)網(wǎng)開發(fā)者大會”,僅需一頓飯錢,即可學(xué)到包括2位QCon講師在內(nèi)的7場干貨分享。詳情及報名:2016互聯(lián)網(wǎng)移動開發(fā)者大會報名通道
掃碼加群還有購票優(yōu)惠。不買票也可以加群,在群里認(rèn)識幾個高手也是合適的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 這兩天摸索了下scrapy,剛看文檔的時候覺得有點生無可戀,scrapy框架個人還是覺得比較難懂的,需要學(xué)習(xí)的地方...
    Treehl閱讀 5,853評論 7 10
  • scrapy學(xué)習(xí)筆記(有示例版) 我的博客 scrapy學(xué)習(xí)筆記1.使用scrapy1.1創(chuàng)建工程1.2創(chuàng)建爬蟲模...
    陳思煜閱讀 13,112評論 4 46
  • 這是一篇小組獎勵文,它匯聚小伙伴們在同一片天空下的不同夢想,很榮幸我有這個機會看到大家的進(jìn)步和努力。 “給力團(tuán)”—...
    小碟小碗小筷子閱讀 477評論 0 0
  • 文 | 私塾先生 前情回顧第一章:窺 | 第二章:探 | 第三章:夢 |第四章:離 “我想好了。”我從沒想過我會如...
    私塾先生lilz閱讀 581評論 0 2
  • 下班回到家,經(jīng)過穿衣鏡時,習(xí)慣性瞅瞅自己,突然發(fā)現(xiàn)一張寫滿疲憊與焦慮的臉,眉頭緊鎖,表情僵硬。心理咯噔一下,最近幾...
    木棉寶貝閱讀 342評論 0 1

友情鏈接更多精彩內(nèi)容