Python爬蟲之Scrapy+Mysql+Mongodb爬豆瓣top250電影

學(xué)習(xí)python時(shí),爬蟲是一種簡單上手的方式,應(yīng)該也是一個(gè)必經(jīng)階段。本項(xiàng)目用Scrapy框架實(shí)現(xiàn)了抓取豆瓣top250電影,并將圖片及其它信息保存下來。爬取豆瓣top250電影不需要登錄、沒有JS解析、而且只有10頁內(nèi)容,用來練手,太合適不過了。

我的開發(fā)環(huán)境

  • WIN10 64位系統(tǒng)
  • Python 3.6.1
  • PyCharm、Sublime Text
  • Mysql、MongoDB,可視化:DbVisualizer、Robomongo

項(xiàng)目目錄

項(xiàng)目目錄
  • spiders/sp_douban.py:處理鏈接,解析item部分
  • items.py:豆瓣top250電影字段
  • middlewares.py、user_agents.py:從維護(hù)的UserAgent池中隨機(jī)選取
  • settings.py:配置文件
  • main.py:免去在命令行輸入運(yùn)行指令

頁面抓取內(nèi)容分析

入口地址:https://movie.douban.com/top250

內(nèi)容區(qū)
span內(nèi)容

如圖所示,抓取信息對應(yīng)如下:

class DoubanTopMoviesItem(scrapy.Item):
    title_ch = scrapy.Field()     # 中文標(biāo)題
    # title_en = scrapy.Field()   # 外文名字
    # title_ht = scrapy.Field()   # 港臺名字
    # detail = scrapy.Field()     # 導(dǎo)演主演等信息
    rating_num = scrapy.Field()   # 分值
    rating_count = scrapy.Field() # 評論人數(shù)
    # quote = scrapy.Field()      # 短評
    image_urls = scrapy.Field()   # 封面圖片地址
    topid = scrapy.Field()        # 排名序號

用xpath取出對應(yīng)路徑,進(jìn)行必要的清洗,去除空格等多余內(nèi)容:

item['title_ch'] = response.xpath('//div[@class="hd"]//span[@class="title"][1]/text()').extract()
en_list = response.xpath('//div[@class="hd"]//span[@class="title"][2]/text()').extract()
item['title_en'] = [en.replace('\xa0/\xa0','').replace('  ','') for en in en_list]
ht_list = response.xpath('//div[@class="hd"]//span[@class="other"]/text()').extract()
item['title_ht'] = [ht.replace('\xa0/\xa0','').replace('  ','') for ht in ht_list]
detail_list = response.xpath('//div[@class="bd"]/p[1]/text()').extract()
item['detail'] = [detail.replace('  ', '').replace('\xa0', '').replace('\n', '') for detail in detail_list]
# 注意:有的電影沒有quote?。。。。。。。。?!
item['quote'] = response.xpath('//span[@class="inq"]/text()').extract()
item['rating_num'] = response.xpath('//div[@class="star"]/span[2]/text()').extract()
# 評價(jià)數(shù)格式:“XXX人評價(jià)”。用正則表達(dá)式只需取出XXX數(shù)字
count_list = response.xpath('//div[@class="star"]/span[4]/text()').extract()
item['rating_count'] = [re.findall('\d+',count)[0] for count in count_list]
item['image_urls'] = response.xpath('//div[@class="pic"]/a/img/@src').extract()
item['topid'] = response.xpath('//div[@class="pic"]/em/text()').extract()

爬取鏈接的三種方式

第二頁的鏈接格式是:https://movie.douban.com/top250?start=25&filter= ,每頁25部電影,所以翻頁就是依次加25

  1. 重寫start_requests方法
base_url = "https://movie.douban.com/top250"
# 共有10頁,格式固定。重寫start_requests方法,等價(jià)于start_urls及翻頁
def start_requests(self):
    for i in range(0, 226, 25):
        url = self.base_url + "?start=%d&filter=" % i
        yield scrapy.Request(url, callback=self.parse)
  1. 初始start_urls加當(dāng)前頁的下一頁
base_url = "https://movie.douban.com/top250"
start_urls = [base_url]
# 取下一頁鏈接,繼續(xù)爬取
new_url = response.xpath('//link[@rel="next"]/@href').extract_first()
if new_url:
    next_url = self.base_url+new_url
    yield scrapy.Request(next_url, callback=self.parse)
  1. 初始start_urls加LinkExtractor 鏈接提取器方法
# 這個(gè)方法需要較大調(diào)整(引入更多模塊、類繼承CrawlSpider、方法命名不能是parse)
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor

base_url = "https://movie.douban.com/top250"
start_urls = [base_url]
rules = [Rule(LinkExtractor(allow=(r'https://movie.douban.com/top250\?start=\d+.*')),
                   callback='parse_item', follow=True)
              ]

下載及保存內(nèi)容

綜合其他人的教程,本項(xiàng)目集成了多種保存方法,包括保存電影封面至本地、存入MYSQL、存入MONGODB。在settings里配置了ITEM_PIPELINES,用到那種方式,就把注釋去掉即可。

  1. 自定義下載圖片方法

    圖片效果
    # 自定義方法下載圖片
    class FirsttestPipeline(object):
        # 電影封面命名:序號加電影名
        def _createmovieImageName(self, item):
            lengh = len(item['topid'])
            return [item['topid'][i] + "-" + item['title_ch'][i] + ".jpg" for i in range(lengh)]
    
        # 另一種命名法,取圖片鏈接中名字
        # def _createImagenameByURL(self, image_url):
        #     file_name = image_url.split('/')[-1]
        #     return file_name
    
        def process_item(self, item, spider):
            namelist = self._createmovieImageName(item)
            dir_path = '%s/%s' % (settings.IMAGES_STORE, spider.name)
            # print('dir_path', dir_path)
            if not os.path.exists(dir_path):
                os.makedirs(dir_path)
            for i in range(len(namelist)):
                image_url = item['image_urls'][i]
                file_name = namelist[i]
                file_path = '%s/%s' % (dir_path, file_name)
                if os.path.exists(file_path):
                    print("重復(fù),跳過:" + image_url)
                    continue
                with open(file_path, 'wb') as file_writer:
                    print("正在下載:"+image_url)
                    conn = urllib.request.urlopen(image_url)
                    file_writer.write(conn.read())
                file_writer.close()
            return item
    
  1. 保存內(nèi)容至MYSQL數(shù)據(jù)庫

    前提是裝好mysql,這部分請自行解決。本項(xiàng)目建表語句:

    CREATE TABLE DOUBANTOPMOVIE (
      topid int(3) PRIMARY KEY ,
      title_ch VARCHAR(50) ,
      rating_num FLOAT(1),
      rating_count INT(9),
      quote VARCHAR(100),
      createdTime TIMESTAMP(6) not NULL DEFAULT CURRENT_TIMESTAMP(6),
      updatedTime TIMESTAMP(6) not NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6)
    ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
    

    具體實(shí)現(xiàn)方法:

    # 保存內(nèi)容至MYSQL數(shù)據(jù)庫
    class DoubanmoviePipeline(object):
        def __init__(self, dbpool):
            self.dbpool = dbpool
    
        @classmethod
        def from_settings(cls, settings):
            dbparams = dict(
                host=settings['MYSQL_HOST'],
                port=settings['MYSQL_PORT'],
                db=settings['MYSQL_DBNAME'],
                user=settings['MYSQL_USER'],
                passwd=settings['MYSQL_PASSWD'],
                charset=settings['MYSQL_CHARSET'],
                cursorclass=MySQLdb.cursors.DictCursor,
                use_unicode=False,
            )
            dbpool = adbapi.ConnectionPool('MySQLdb', **dbparams)  # **表示將字典擴(kuò)展為關(guān)鍵字參數(shù)
            return cls(dbpool)
    
        # pipeline默認(rèn)調(diào)用
        def process_item(self, item, spider):
            # 調(diào)用插入的方法
            query=self.dbpool.runInteraction(self._conditional_insert,item)
            # 調(diào)用異常處理方法
            query.addErrback(self._handle_error,item,spider)
            return item
    
        def _conditional_insert(self, tx, item):
            sql = "insert into doubantopmovie(topid,title_ch,rating_num,rating_count) values(%s,%s,%s,%s)"
            lengh = len(item['topid'])
            for i in range(lengh):
                params = (item["topid"][i], item["title_ch"][i], item["rating_num"][i], item["rating_count"][i])
                tx.execute(sql, params)
    
        def _handle_error(self, e):
            print(e)
    
  1. 保存內(nèi)容至MONGODB數(shù)據(jù)庫

    前提是裝好mongodb,這部分請自行解決??梢暬ぞ咄扑]Robomongo,本項(xiàng)目保存結(jié)果及實(shí)現(xiàn)方法:

    mongodb截圖
    # 保存內(nèi)容至MONGODB數(shù)據(jù)庫
    class MongoDBPipeline( object):
        mongo_uri_no_auth = 'mongodb://localhost:27017/' # 沒有賬號密碼驗(yàn)證
        database_name = 'yun'
        table_name = 'coll'
        client = MongoClient(mongo_uri_no_auth)  # 創(chuàng)建了與mongodb的連接
        db = client[database_name]
        table = db[table_name]  # 獲取數(shù)據(jù)庫中表的游標(biāo)
    
        def process_item(self, item, spider):
            valid = True
            for data in item:
              if not data:
                  valid = False
                  raise DropItem("Missing {0}!".format(data))
            if valid:
                  self.table.insert(dict(item))
            return item
    
  2. 用內(nèi)置的ImagesPipeline類下載圖片

    Scrapy自帶的ImagesPipeline 實(shí)現(xiàn)起來也很簡單。不過,比較下來,速度不及自定義的方法,不知是否哪里寫的不對。若有高手發(fā)現(xiàn),歡迎指出原因。

    from scrapy.contrib.pipeline.images import ImagesPipeline
    from scrapy.http import Request
    from scrapy.exceptions import DropItem
    
    # 用Scrapy內(nèi)置的ImagesPipeline類下載圖片
    class MyImagesPipeline(ImagesPipeline):
        def file_path(self, request, response=None, info=None):
            image_name = request.url.split('/')[-1]
            return 'doubanmovie2/%s' % (image_name)
    
        # 從item獲取url,返回request對象給pipeline處理
        def get_media_requests(self, item, info):
            for image_url in item['image_urls']:
                yield Request(image_url)
    
        # pipeline處理request對象,完成下載后,將results傳給item_completed
        def item_completed(self, results, item, info):
            image_paths = [x['path'] for ok, x in results if ok]
            # print(image_paths)
            if not image_paths:
                raise DropItem("Item contains no images")
            # item['image_paths'] = image_paths
            return item
    

其它

from scrapy.selector import Selector
Selector(response).xpath('//span/text()').extract()
# 等價(jià)于下面寫法:
response.selector.xpath('//span/text()').extract() # .selector 是response對象的屬性
# 也等價(jià)于下面寫法(進(jìn)一步簡化):
response.xpath('//span/text()').extract()

完整項(xiàng)目代碼見Github

覺得對你有所幫助的話,給個(gè)star ?吧

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,362評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,577評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,486評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,852評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,600評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,944評論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,944評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,108評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,652評論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,385評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,616評論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,111評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,798評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,205評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,537評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,334評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,570評論 2 379

推薦閱讀更多精彩內(nèi)容