python爬取琳琅社區整站視頻(一晚6000部)

琳琅社區(傳聞中最受男人喜愛的網站),哼哼,我倒要看看是不是真的

該項目用於爬取琳琅社區整站視頻(僅供學習)

主要使用:python3.7 + scrapy2.19 + Mysql 8.0 + win10

  • 首先確定需要爬取的內容,定義item
class LinglangItem(scrapy.Item):
	#視頻屬於哪個模塊
    video_belong_module = scrapy.Field()
    #視頻播放頁面url
    video_url = scrapy.Field()
    #視頻標題
    video_title = scrapy.Field()
    #視頻真實m3u8地址
    video_m3u8_url =  scrapy.Field()
  • 然後編寫爬蟲文件
    構造初始url的解析函數,得到琳琅網站的視頻分類請求,並在本地生成存儲的主目錄
def parse(self, response):
    # 創建主目錄
    if not os.path.exists(self.base_dir):
        os.mkdir(self.base_dir)
    all_module_url = response.xpath('//div[@id="head_nav"]/div/div[@class="left_nav"]/a/@href').extract()[1:]
    #得到所有模塊(最新,動漫。。。)的絕對url
    all_module_url = [self.start_urls[0] + url for url in all_module_url]
    # 構造所有模塊頁的請求
    for page_url in all_module_url:
        # 引擎判斷該數據爲一個請求,給調度器,
        # 如果是其他格式比如列表,引擎不能識別,只能通過我們的命令-o處理
        yield scrapy.Request(page_url, callback=self.page_parse)

定義具體模塊頁面的解析函數,支持分頁爬取:

def page_parse(self,response):

    # 得到該頁面所有視頻的url,title,視頻m3u8地址 (20個)
    video_urls = response.xpath('//ul[contains(@class,"piclist")]/li/a/@href').extract()
    video_titles =  response.xpath('//ul[contains(@class,"piclist")]/li/a/@title').extract()
   
    video_m3u8_url_ls = response.xpath('//ul[contains(@class,"piclist")]/li/a/@style').extract()
    # 該視頻所在模塊
    video_belong_module =  response.xpath('//a[contains(@class,"on")]/text()').extract_first()
    for index,video_m3u8_url in enumerate(video_m3u8_url_ls):
        # 最好yield一個item就重新創建一個,否則可能導致一些問題,比如名字重複
        item = dict()
        ls = video_m3u8_url.split('/')
        #https://bbb.188370aa.com/20191014/WLDsLTZK/index.m3u8
        #0       1   2                     3       4      5
        # 得到絕對m3u8_url
        try:
            m3u8_url = self.m3u8_domain + ls[3] + '/' +ls[4] + '/index.m3u8'
        except:
            continue
        item['video_belong_module'] = video_belong_module
        item['video_url'] = self.start_urls[0] + video_urls[index]

        #教訓:有些名字後面帶空格,刪的時候找不到文件
        # item['video_title'] = video_titles[index].strip()
        item['video_title'] = video_titles[index].strip().replace('.','')
        # item['video_m3u8_url'] = m3u8_url
        self.num += 1
        print('當前是第 %s 個視頻: %s' % (self.num, item['video_title']))
        #創建每個視頻目錄
        module_name = video_belong_module
        file_name = item['video_title']
        # module_path = os.path.join(self.base_dir, module_name)
        # video_path = os.path.join(module_path, file_name)
        module_path = self.base_dir + module_name + '/'
        video_path = module_path + file_name +'/'
        if not os.path.exists(video_path):
            try:
                os.makedirs(video_path)
            except:
                video_path = module_path + str(random()) + '/'
                os.makedirs(video_path)

        yield scrapy.Request(m3u8_url, callback=self.m3u8_parse, meta={'video_path':video_path,'item':item})
    try:
        # 得到下一頁的a標籤selector對象
        next_page_selector = response.xpath('//div[@class="pages"]/a')[-2]
        # 如果有下一頁則向下一頁發起請求,尾頁的下一頁a標籤沒有href屬性
        next_page = next_page_selector.xpath('./@href').extract_first()
        if  next_page:
            next_page_url = self.start_urls[0] + next_page
            yield scrapy.Request(next_page_url, callback=self.page_parse)
    except:
        pass

返回item給管道文件:

ef m3u8_parse(self,response):
        item = LinglangItem()
        for k,v in response.meta['item'].items():
            item[k] = v
        # response.text得到m3u8文件內容字符串
        # 得到最新的m3u8文件url
        real_url = re.findall(r'https:.*?m3u8', response.text)[-1]
        item['video_m3u8_url'] = real_url
        # yield返回給引擎的時候會判斷 item 的數據類型是不是item類型如果是則返回給piplines
        yield item

實現一個去重管道:

#實現去重Item Pipeline 過濾重複數據
class DuplicatesPipline(object):
    #只在第一個item來時執行一次,可選實現,做參數初始化等
    def __init__(self):
        self.video_title_set = set()
    def process_item(self,item,spider):
        video_title = item['video_title']
        if video_title in self.video_title_set:
            item['video_title'] = item['video_title'] + str(random())
        self.video_title_set.add(video_title)
        #表示告訴引擎,我這個item處理完了,可以給我下一個item
        return item
    #然後去settings中啓用DuplicatesPipline

再實現將數據存入mysql的存儲管道,此處也可選擇其他種類數據庫進行存儲:

#將item數據存入數據庫
class MySqlPipeline(object):
    def __init__(self,database):
        self.database = database

    # 該方法可以在settings裏面拿到一些配置信息
    @classmethod
    def from_crawler(cls, crawler):
        # 相當於返回一個MySqlPipeline對象
        return cls(
            # 得到settings裏面的對應配置信息並返回,當作init的參數
            database=crawler.settings.get('DATABASE')
        )

    #當spider被開啓時,這個方法被調用, 連接數據庫
    def open_spider(self, spider):
        self.db = pymysql.connect(host='localhost',port=3306,user='root',password='123456',database=self.database, charset='utf8')
        self.cursor = self.db.cursor()
        print('數據庫:',type(self.db),type(self.cursor))


    def process_item(self,item,spider):

        sql = "insert into video_info values(%s,%s,%s,%s);"
        values = tuple(dict(item).values())
        #執行成功返回1
        self.cursor.execute(sql,values)
        # 前面只是把數據寫到緩存裏,執行commit命令寫到數據庫中
        self.db.commit()
        return item
        # 然後去settings中啓用MySqlPipeline,這裏暫時不啓用


    # 當spider被關閉時,這個方法被調用,關閉數據庫
    def close_spider(self, spider):
        self.cursor.close()
        self.db.close()

其實呢,到這已經能夠進行爬取了。但是我們利用scrapy對該網站頻繁發起這麼多次請求,對方服務器判定我們爲爬蟲時,會強行關閉與我們之間的連接。

雖然scrapy會將這些沒有爬取成功的請求重新放回調度器,等待之後連接成功再發送請求,但是這樣會浪費我們一些時間。

爲了提高效率,當本地請求失敗後,我們可以在下載中間件中使用動態代理重新發起請求:

  def process_response(self, request, response, spider):
        # Called with the response returned from the downloader.

        # Must either;
        # - return a Response object
        # - return a Request object
        # - or raise IgnoreRequest
        # 如果返回的response狀態不是200,重新生成當前request對象
        if response.status != 200:
            print('使用代理-------------------------')
            headers = {
                'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36'
            }
            # 對當前request加上代理
            request.headers = headers
            request.meta['proxy'] = 'http://' + self.random_ip()
            return request
        return response

最後啓動爬蟲,等待爬蟲結束,查看數據庫,滿滿的收穫~
可以看出該網站共有5997條視頻,感覺沒有想象的那麼多啊可以看出該網站共有5997條視頻,感覺沒有想象的那麼多啊,網站url不敢貼出來,小怕怕,哈哈。

實踐出真知,這種做着有精神的網站更是練手的好目標,就是身體一天不如一天,可能是熬夜吧。。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章