Python+Scrapy爬取騰訊新聞首頁全部新聞及評論

前言html

這篇博客寫的是實現的一個爬取騰訊新聞首頁全部的新聞及其全部評論的爬蟲。選用Python的Scrapy框架。這篇文章主要討論使用Chrome瀏覽器的開發者工具獲取新聞及評論的來源地址。git

Chrome的開發者工具(或Firefox的web控制檯)是個頗有用的工具,你能夠經過它清楚的看到你在訪問一個網站的過程當中瀏覽器發送了哪些信息,接收了哪些信息。而在咱們編寫爬蟲的時候,就須要知道咱們須要爬取的內容來自哪裏,來自哪一個連接。github

 

正文web

騰訊新聞首頁上的新聞有三種連接格式json

一種是:https://news.qq.com/a/time/newsID.htmapi

如:https://news.qq.com/a/20180414/010445.htm瀏覽器

一種是:http://new.qq.com/omn/time/newsID.htmlapp

如:http://new.qq.com/omn/20180415/20180415A0Z5P3.html框架

一種是:http://new.qq.com/omn/newsIDdom

如:http://new.qq.com/omn/20180414A000MX00

 

其中:
time:新聞發佈日期,第三種新聞連接沒有這個值。
newsID:新聞頁面的ID,第一種新聞的ID只包含數字,後兩種包含數字和字母

 

這三種格式的新聞連接都能在騰訊新聞首頁的源代碼中獲得,如圖:

獲得了新聞頁面以後,接下來是獲得新聞的正文,前面兩種新聞的正文及其餘信息能夠直接在頁面的源代碼中得到。第三種就比較麻煩了,下文會講到。另外還要經過新聞頁面獲得評論頁面。

三種格式的新聞的評論頁連接的格式是相同的
都爲http://coral.qq.com/cmtid
如:http://coral.qq.com/2572597712
其中的cmtid爲一串數字,標識每一條新聞的評論頁面。咱們須要在新聞頁面中找到這個值,前面兩種新聞比較方便,cmtid以及其餘新聞信息都在頁面源碼中,可是第三種新聞就不一樣了,頁面源碼中沒有咱們想要的東西。
這時候就要使用開發者工具來獲得第三種新聞評論頁的cmtid以及新聞正文。
在第三種新聞的新聞頁面http://new.qq.com/omn/newsid,按F12(或右鍵->檢查)調出開發者工具,點擊network,F5快捷鍵刷新。如圖:

而後在找到包含所須要信息的地址。如圖:

在Headers欄查看地址,如圖:

能夠獲得第三種新聞的cmtid以及正文信息經過這個地址返回:
http://openapi.inews.qq.com/getQQNewsNormalContent?id=newsid&chlid=news_rss&refer=mobilewwwqqcom&otype=jsonp&ext_data=all&srcfrom=newsapp&callback=getNewsContentOnlyOutput
如:http://openapi.inews.qq.com/getQQNewsNormalContent?id=20180414A000MX00&chlid=news_rss&refer=mobilewwwqqcom&otype=jsonp&ext_data=all&srcfrom=newsapp&callback=getNewsContentOnlyOutput

其中newsid就是新聞的id,咱們能夠經過這個連接獲得cmtid、正文內容。

在獲得了新聞的cmtid後,而後就要分析獲得評論信息的來源地址了
在評論頁http://coral.qq.com/cmtid調出開發者工具,刷新獲得返回的信息。如圖:

在Headers欄查看地址,如圖:

能夠獲得評論經過下面這個地址返回
http://coral.qq.com/article/2530433473/comment/v2?callback=_articlecommentv2&orinum=10&oriorder=t&pageflag=1&cursor=0&scorecursor=0&orirepnum=2&reporder=o&reppageflag=1&source=1&_=1522383466213
其中
2530433473:表示評論頁ID。
orinum=10:表示返回評論的數目爲10,這個值最大爲30,也就是一個頁面最多返回30個評論。
oriorder=t:表示返回的評論按時間排序 ,o表示按熱度排序
orirepnum=2:表示每條評論的回覆評論數最多爲2,也就是樓中樓最多兩層
cursor=0:起始值爲0,以後根據返回頁面中last的值,獲得下一個評論頁面。
reporder=t:同oriorder=t。

以上這些值能夠根據本身的需求更改,其餘的無需更改。其中爲了獲得全部評論,須要不斷更改cursor的值,該值能夠經過返回的評論頁中last的值更新。
以上就是數據來源地址的獲取,接下來就是爬蟲的具體實現了。

 

爬蟲的具體實現
該爬蟲分爲兩個模塊,模塊一是爬取新聞首頁全部的新聞,獲取全部新聞的正文,新聞id、評論頁id等信息
模塊二是根據獲取的新聞id、評論頁id,逐個爬取每一個新聞的全部評論。
模塊一的主要代碼

# -*- coding: utf-8 -*-
from scrapy.spiders import Spider  
from scrapy.http import Request  
from scrapy.selector import Selector  
from test1.items import NewsItem,ListCombiner
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
import requests
import re
import json

class TencentNewsSpider(CrawlSpider):
    name = 'tencent_news_spider'
    allowed_domains = ['new.qq.com','news.qq.com']
    start_urls = [
        'http://news.qq.com'
    ]
    url_pattern1= r'(.*)/a/(\d{8})/(\d+)\.htm'
    url_pattern2=r'(.*)/omn/(.+)\.html'
    url_pattern3=r'(.*)/omn/([A-Z0-9]{16,19})'
    url_pattern4=r'(.*)/omn/(\d{8})/(.+)\.html'
    rules = (
        Rule(LinkExtractor(allow=(url_pattern1)),'parse_news1'),
        Rule(LinkExtractor(allow=(url_pattern2)),'parse_news2'),
        Rule(LinkExtractor(allow=(url_pattern3)),'parse_news3'),
    )

    def parse_news1(self, response):
        sel = Selector(response)
        print(response.url)
        pattern = re.match(self.url_pattern1, str(response.url))
        item = NewsItem()
        item['source'] = 'tencent'#pattern.group(1)
        item['date'] = pattern.group(2)
        item['newsId'] = pattern.group(3)
        item['cmtId'] = (sel.re(r"cmt_id = (.*);"))[0] # unicode string須要判斷有沒有cmtId,由於頁面有可能爲空
        item['comments'] = {'link':str('http://coral.qq.com/')+item['cmtId']}
        item['contents'] = {'link':str(response.url), 'title':u'', 'passage':u''}
        item['contents']['title'] = sel.xpath('//h1/text()').extract()[0]
        item['contents']['passage'] = ListCombiner(sel.xpath('//p/text()').extract())
        return item


    def parse_news2(self,response):
        sel = Selector(response)
        pattern = re.match(self.url_pattern4, str(response.url))
        item=NewsItem()
        item['source'] = 'tencent'#pattern.group(1)
        item['date'] = pattern.group(2)
        item['newsId'] = pattern.group(3)
        item['cmtId'] = (sel.re(r"\"comment_id\":\"(\d*)\","))[0]
        item['comments'] = {'link':str('http://coral.qq.com/')+item['cmtId']}
        item['contents'] = {'link':str(response.url), 'title':u'', 'passage':u''}
        item['contents']['title'] = sel.xpath('//h1/text()').extract()[0]
        item['contents']['passage'] = ListCombiner(sel.xpath('//p/text()').extract())
        return item

    def parse_news3(self,response):
        item = NewsItem()
        print(response.url)
        str1='http://openapi.inews.qq.com/getQQNewsNormalContent?id='
        str2='&chlid=news_rss&refer=mobilewwwqqcom&otype=jsonp&ext_data=all&srcfrom=newsapp&callback=getNewsContentOnlyOutput'
        pattern = re.match(self.url_pattern3, str(response.url))
        date=re.search(r"(\d{8})",pattern.group(2))#匹配時間
        item['source'] = 'tencent'#pattern.group(1)
        item['date'] = date.group(0)
        item['newsId'] = pattern.group(2)
        print(pattern.group(2))
        headers = {
        'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36'
        }
        out=self.getHTMLText(str1+pattern.group(2)+str2,headers)
        g=re.search("getNewsContentOnlyOutput\\((.+)\\)", out)
        out=json.loads(g.group(1))
        item['cmtId'] =out["cid"]
        item['comments'] = {'link':str('http://coral.qq.com/')+item['cmtId']}
        item['contents'] = {'link':str(response.url), 'title':u'', 'passage':u''}
        item['contents']['title'] = out["title"]
        item['contents']['passage'] =out["ext_data"]["cnt_html"]
        return item

    def getHTMLText(self,url,headers):
        try:
            r=requests.get(url, headers=headers)
            r.raise_for_status()
            r.encoding=r.apparent_encoding
            return r.text
        except:
            print("產生異常")

 

模塊二的主要代碼
如下是爬取評論的函數:

# -*- coding: utf-8 -*-
import requests
import re
import json
import codecs
import os
import datetime

# 爬取新聞評論id爲commentid,日期爲date,新聞id爲newsID的全部評論
def crawlcomment(commentid,date,newsID):
    url1='http://coral.qq.com/article/'+commentid+'/comment/v2?callback=_articlecommentv2&orinum=30&oriorder=t&pageflag=1&cursor='
    url2='&orirepnum=10&_=1522383466213'
    # 必定要加頭要否則沒法訪問
    headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36'
    }

    dir=os.getcwd();
    comments_file_path=dir+'/docs/tencent/' + date+'/'+newsID+'_comments.json'

    news_file = codecs.open(comments_file_path, 'a', 'utf-8')
    response=getHTMLText(url1+'0'+url2,headers)

    while 1:
        g=re.search("_articlecommentv2\\((.+)\\)", response)
        out=json.loads(g.group(1))
        if not out["data"]["last"]:
            news_file.close()
            print("finish!")
            break;
        for i in out["data"]["oriCommList"]:
            time=str(datetime.datetime.fromtimestamp(int(i["time"])))#將unix時間戳轉化爲正常時間
            line = json.dumps(time+':'+i["content"],ensure_ascii=False)+'\n'
            news_file.write(line)
            print(i["content"])

        url=url1+out["data"]["last"]+url2#獲得下一個評論頁面連接
        print(url)
        response=getHTMLText(url,headers)

def getHTMLText(url,headers):
    try:
        r=requests.get(url, headers=headers)
        r.raise_for_status()
        r.encoding=r.apparent_encoding
        return r.text
    except:
        return "產生異常 "

 

效果截圖

爬取了132條新聞,如圖:

新聞正文,如圖:

新聞評論,如圖:

可到個人github獲取全部代碼:

https://github.com/Hahallo/CrawlTencentNewsComments

相關文章
相關標籤/搜索