目標說明
利用scrapy抓取中新網新聞,關於自然災害滑坡的全部國內新聞;要求主題為滑坡類新聞,包含災害造成的經濟損失等相關內容,並結合textrank算法,得到每篇新聞的關鍵詞,便於后續文本挖掘分析。
網站分析
目標網站:http://sou.chinanews.com/advSearch.do
結合中新搜索平台的高級搜索的特點,搜索關鍵詞設置為:滑坡 經濟損失(以空格隔開),設置分類頻道為國內,排序方式按照相關度。得到所有檢索到的新聞如下:
共1000多條數據。
分析網站特點發現,給請求為異步加載,通過抓包工具Fiddler得到:
分析:
POST提交,每次提交目標的url為:http://sou.chinanews.com/search.do
提交參數如上所示,其中q表示關鍵詞(抓包測試時只輸入了一個關鍵詞);
ps表示每次顯示的調試,adv=1表示高級搜索;day1,day2表示搜索時間,默認不寫表示全部時間,channel=gn表示國內;
繼續點擊下一頁,通過對照得到一個新的參數:start,其中當start=0時,默認省略,表示第一頁,每次下一頁都增加10(設置的頁面顯示10)
分析得到,每次點擊下一頁都是一個POST提交,參數相同,不同的是start
代碼邏輯
使用命令:scrapy start project NewsChina創建項目,編寫items.py文件,明確抓取字段(常用套路,第一步都是明確抓取字段)。這里只抓取標題和正文。常規操作:添加數據來源和抓取時間信息。
# -*- coding: utf-8 -*- # Define here the models for your scraped items # # See documentation in: # https://doc.scrapy.org/en/latest/topics/items.html import scrapy class NewschinaItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() # 數據來源 source = scrapy.Field() # 抓取時間 utc_time = scrapy.Field() # 新聞標題 title = scrapy.Field() # 新聞內容 content = scrapy.Field() # 關鍵詞 keywords = scrapy.Field()
明確抓取字段后,使用命令:scrapy genspider newsChina 生成爬蟲,開始編寫爬蟲邏輯,結合抓取網站的特點,做以下操作:
針對該網站的反爬措施,添加請求延遲、重試次數等待配置;
通過修改POST請求的time_scope字段,得到每一頁數據,並解析數據中詳情頁的鏈接,然后對詳情頁鏈接請求,解析待抓取數據;
至於循環抓取和終止循環條件,結合實際網站各有不同,在代碼中已有說明。
# -*- coding: utf-8 -*- import re import scrapy from NewsChina.items import NewschinaItem class NewschinaSpider(scrapy.Spider): name = 'newsChina' # allowed_domains = ['sou.chinanews.com'] # start_urls = ['http://http://sou.chinanews.com/'] #爬蟲設置 # handle_httpstatus_list = [403] # 403錯誤時拋出異常 custom_settings = { "DOWNLOAD_DELAY": 2, "RETRY_ENABLED": True, } page = 0 # 提交參數 formdata = { 'field': 'content', 'q': '滑坡 經濟損失', 'ps': '10', 'start': '{}'.format(page * 10), 'adv': '1', 'time_scope': '0', 'day1': '', 'day2': '', 'channel': 'gn', 'creator': '', 'sort': '_score' } # 提交url url = 'http://sou.chinanews.com/search.do' def start_requests(self): yield scrapy.FormRequest( url=self.url, formdata=self.formdata, callback=self.parse ) def parse(self, response): try: last_page = response.xpath('//div[@id="pagediv"]/span/text()').extract()[-1] # 匹配到尾頁退出迭代 if last_page is '尾頁': return except: # 當匹配不到last_page時,說明已經爬取所有頁面,xpath匹配失敗 # 拋出異常,這就是我們的循環終止條件 # print("last_page:", response.url) return link_list = response.xpath('//div[@id="news_list"]/table//tr/td/ul/li/a/@href').extract() for link in link_list: if link: item = NewschinaItem() # 訪問詳情頁 yield scrapy.Request(link, callback=self.parse_detail, meta={'item': item}) # 循環調用,訪問下一頁 self.page += 1 # 下一頁的開始,修改該參數得到新數據 self.formdata['start'] = '{}'.format(self.page * 10) yield scrapy.FormRequest( url=self.url, formdata=self.formdata, callback=self.parse ) # 從詳情頁中解析數據 def parse_detail(self, response): """ 分析發現,中新網年份不同,所以網頁的表現形式不同, 由於抓取的是所有的數據,因此同一個xpath可能只能匹配到部分的內容; 經過反復測試發現提取規則只有如下幾條。提取標題有兩套規則 提取正文有6套規則。 :param response: :return: """ item = response.meta['item'] # 提取標題信息 if response.xpath('//h1/text()'): item['title'] = response.xpath('//h1/text()').extract_first().strip() elif response.xpath('//title/text()'): item['title'] = response.xpath('//title/text()').extract_first().strip() else: print('title:', response.url) # 提取正文信息 try: if response.xpath('//div[@id="ad0"]'): item['content'] = response.xpath('//div[@id="ad0"]').xpath('string(.)').extract_first().strip() elif response.xpath('//div[@class="left_zw"]'): item['content'] = response.xpath('//div[@class="left_zw"]').xpath('string(.)').extract_first().strip() elif response.xpath('//font[@id="Zoom"]'): item['content'] = response.xpath('//font[@id="Zoom"]').xpath('string(.)').extract_first().strip() elif response.xpath('//div[@id="qb"]'): item['content'] = response.xpath('//div[@id="qb"]').xpath('string(.)').extract_first().strip() elif response.xpath('//div[@class="video_con1_text_top"]/p'): item['content'] = response.xpath('//div[@class="video_con1_text_top"]/p').xpath('string(.)').extract_first().strip() else: print('content:', response.url) except: # 測試發現中新網有一個網頁的鏈接是空的,因此提前不到正文,做異常處理 print(response.url) item['content'] = '' yield item
編寫中間件,添加隨機頭信息:
# -*- coding: utf-8 -*- # Define here the models for your spider middleware # # See documentation in: # https://doc.scrapy.org/en/latest/topics/spider-middleware.html import random from NewsChina.settings import USER_AGENTS as ua class NewsChinaSpiderMiddleware(object): def process_request(self, request, spider): """ 給每一個請求隨機分配一個代理 :param request: :param spider: :return: """ user_agent = random.choice(ua) request.headers['User-Agent'] = user_agent
編寫數據保存邏輯:
結合python的jieba模塊的textrank算法,實現新聞的關鍵詞抽取,並保存到excel或數據庫中
# -*- coding: utf-8 -*- # Define your item pipelines here # # Don't forget to add your pipeline to the ITEM_PIPELINES setting # See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html from datetime import datetime from jieba import analyse from openpyxl import Workbook import pymysql class KeyswordPipeline(object): """ 添加數據來源及抓取時間; 結合textrank算法,抽取新聞中最重要的5個詞,作為關鍵詞 """ def process_item(self, item, spider): # 數據來源 item['source'] = spider.name # 抓取時間 item['utc_time'] = str(datetime.utcnow()) content = item['content'] keywords = ' '.join(analyse.textrank(content, topK=5)) # 關鍵詞 item['keywords'] = keywords return item class NewsChinaExcelPipeline(object): """ 數據保存 """ def __init__(self): self.wb = Workbook() self.ws = self.wb.active self.ws.append(['標題', '關鍵詞', '正文', '數據來源', '抓取時間']) def process_item(self, item, spider): data = [item['title'], item['keywords'], item['content'], item['source'], item['utc_time']] self.ws.append(data) self.wb.save('./news.xls') return item # class NewschinaPipeline(object): # def __init__(self): # self.conn = pymysql.connect( # host='.......', # port=3306, # database='news_China', # user='z', # password='136833', # charset='utf8' # ) # # 實例一個游標 # self.cursor = self.conn.cursor() # # def process_item(self, item, spider): # sql = """ # insert into ChinaNews(ID, 標題, 關鍵詞, 正文, 數據來源, 抓取時間) # values (%s, %s, %s, %s, %s, %s);""" # # values = [ # item['title'], # item['keywords'], # item['content'], # # item['source'], # item['utc_time'] # ] # # self.cursor.execute(sql, values) # self.conn.commit() # # return item # # def close_spider(self, spider): # self.cursor.close() # self.conn.close()
運行結果
完成代碼
參見:https://github.com/zInPython/NewsChina