天天看點

Scrapy爬取豆瓣分類電影

由于畢業設計推薦系統需要大量電影資訊資料,從豆瓣電影爬取了一萬多條電影資料,記錄一下第一次爬資料,同時感謝豆瓣電影平台提供的資料。

爬取思想:豆瓣選電影頁面用的是JS動态渲染,用谷歌浏覽器檢視點選加載時發送的URL,解析拼接為新的URL,在Scrapy架構中以該URL為入口,每次擷取到20個電影資料,再分别通路20個電影的URL位址擷取到詳細的電影資料,用Xpath解析html頁面。

具體步驟如下:

1.解析拼接url

通路豆瓣頁面點選加載更多,谷歌檢視此時背景發現它發送了一個請求:

https://movie.douban.com/j/new_search_subjects?sort=U&range=0,10&tags=&start=20
           

通過這個URL請求可以擷取到20條JSON格式的電影資料,每次通過對start參數自增20擷取到資料。

2.擷取資料

請求url擷取到如下的JSON格式資料:

Scrapy爬取豆瓣分類電影

解析擷取JSON格式資料: 

dicts=json.loads(response.body)['data']
           

循環20個電影清單通路每個電影詳情頁的url擷取HTML頁面

movie_url=dict['url']
movie_html=requests.get(movie_url)
root = etree.HTML(movie_html.content)
movies=root.xpath('//div[@id="content"]')
           

用Xpath解析HTML頁面元素擷取到資料,并且給item

movie_id=movie_url[33:-1]
                    movie_title=each.xpath('.//h1/span[1]/text()')
                    movie_title = ",".join(movie_title)
                    release_year=each.xpath('.//h1/span[2]/text()')
                    release_year = ",".join(release_year).split(')')[0].split('(')[1]
                    movie_img=each.xpath('.//div[@id="mainpic"]/a/img/@src')
                    movie_img = ",".join(movie_img)
                    director=each.xpath('.//div[@id="info"]/span[1]/span[2]/a/text()')
                    director=",".join(director)
                    screenwriter=each.xpath('.//div[@id="info"]/span[2]/span[2]/a/text()')
                    screenwriter = ",".join(screenwriter)
                    starring=each.xpath('.//div[@id="info"]//a[@rel="v:starring"]/text()')
                    starring = ",".join(starring)
                    type=each.xpath('.//div[@id="info"]/span[@property="v:genre"]/text()')
                    type = ",".join(type)
                    production_country=each.xpath('.//div[@id="info"]/text()[preceding-sibling::span[text()="制片國家/地區:"]][following-sibling::br]')[0]
                    language=each.xpath('.//div[@id="info"]/text()[preceding-sibling::span[text()="語言:"]][following-sibling::br]')[0]
                    release_time=each.xpath('.//div[@id="info"]/span[@property="v:initialReleaseDate"]/text()')
                    release_time = ",".join(release_time)
                    length=each.xpath('.//div[@id="info"]/span[@property="v:runtime"]/text()')
                    length = ",".join(length)
                    url=movie_url
                    imdb_link=each.xpath('.//div[@id="info"]/a/@href')
                    imdb_link = ",".join(imdb_link)
                    alias=each.xpath('.//div[@id="info"]/text()[preceding-sibling::span[text()="又名:"]][following-sibling::br]')
                    if alias:
                        alias=alias[0]
                    score=each.xpath('.//div[@id="interest_sectl"]/div[1]/div[2]/strong/text()')
                    score = ",".join(score)
                    synopsis=each.xpath('.//div[@id="link-report"]/span/text()')
                    synopsis = ",".join(synopsis).replace(" ","").replace("\u3000","")
           

重構URL位址,再進行擷取電影資料

url = response.url
        url = url.split('=')
        url[-1] = str(int(url[-1])+20)
        next_url = '='.join(url)
        yield scrapy.Request(next_url,callback=self.parse,dont_filter=True)
           

3.将資料存到Mysql資料庫

class DoubanmoviePipeline(object):

    def __init__(self,dbpool):
        self.dbpool=dbpool

    @classmethod
    def from_settings(cls,settings):
        dbparams=dict(
            host=settings['MYSQL_HOST'],
            db=settings['MYSQL_DBNAME'],
            user=settings['MYSQL_USER'],
            password=settings['MYSQL_PASSWORD'],
            charset='utf8',
            cursorclass=pymysql.cursors.DictCursor,
            use_unicode=False,
        )
        dbpool=adbapi.ConnectionPool('pymysql',**dbparams)
        return cls(dbpool)

    def process_item(self, item, spider):
        asynitem = copy.deepcopy(item)
        if isinstance(item,DoubanmovieItem):
            query=self.dbpool.runInteraction(self._conditional_insert_movie,asynitem)
            query.addErrback(self._handle_error,asynitem,spider)
        elif isinstance(item,MovieRcmdItem):
            query=self.dbpool.runInteraction(self._conditional_insert_moviercmd,asynitem)
            query.addErrback(self._handle_error,asynitem,spider)
        return asynitem

    def _conditional_insert_movie(self,tx,item):
        sql="insert into movie(movie_id,movie_title,movie_img,director,screenwriter,starring,type,production_country,language,release_time,length,release_year,url,imdb_link,alias,score,synopsis) values(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)"
        params=(item['movie_id'], item['movie_title'],item['movie_img'],item['director'],item['screenwriter'],item['starring'],item['type'], item['production_country'],item['language'],item['release_time'],item['length'],item['release_year'],item['url'], item['imdb_link'],item['alias'],item['score'],item['synopsis'])
        tx.execute(sql,params)
           

注意對資料進行深拷貝,否則爬取速度太快已爬取的資料還沒存入資料庫,後面的資料就已經覆寫掉前面的資料。

4.反爬蟲政策

  • 在setting裡面禁用cookie
  • 使用IP代理,購買IP代理池(這裡買的是阿布雲的IP代理)或者網上找IP代理池
  • 随機UserAgent頭

     最後爬取了一萬多條資料,如果隻是做推薦系統的話,不需要太多的國家等這麼多詳細資訊,網上也有很多已經爬取好的資料,我這裡是做的Web推薦系統,是以需要更多詳細的電影資料才自己按需求爬取的資料。另外,如果隻是需要少量資料的話可以使用一些爬蟲軟體,比如八爪魚、火車頭、後羿采集器。我個人比較喜好後羿采集器,感覺定制規則友善很多。