通過關鍵詞爬取人民網新聞入庫並實現url去重

簡介

今天的任務是通過關鍵詞爬取人民網的新聞,並存入數據庫,同時實現url去重效果。

所需模塊

requests
selenium
lxml
re
pymysql
redis

數據庫創建

由於數據要存入數據庫,同時還要實現去重效果,我們需要用到mysql和redis數據庫

  • 明確所需要提取的信息
    我們首先創建一個mysql數據庫
create database news_db;

然後創建一個存新聞簡介的表
表包含以下屬性:
id:主鍵
title:標題
url:新聞詳情url
update_time:發佈時間

 create table brief_news(id int primary key auto_increment,title varchar(70),url varchar(100),update_time datetime);

再創建一個存放新聞詳細內容的表
表包含以下屬性:
news_id:外鍵
content:新聞內容

create table detail_news(news_id int not null,content text,foreign key (news_id) references brief_news(id));

網頁分析

通過selenium我們可以拿到網頁渲染的html,通過xpath提取每條新聞的url,標題,通過正則提取新聞時間
在這裏插入圖片描述
分析幾個新聞詳情頁,發現新聞的主體內容都在p標籤下。
在這裏插入圖片描述

程序分模塊講解

導入所需要的庫

from selenium.webdriver import Chrome
from selenium.webdriver.common.keys import Keys
from selenium.webdriver import ChromeOptions
import requests
from lxml.html import etree
import re
import pymysql
import redis
import hashlib
#以下兩行用於忽略requests證書警告
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

Redis類和Hashencode類

用於對url進行hash編碼和去重,這裏就不詳細講了,之前已經寫過一篇利用Redis去重的博客,這裏也就是照着搬過來用。

class Redis():
    def __init__(self,host):
        self.con = redis.Redis(
            host=host, #ip地址
            port=6379, #端口號,默認爲6379
            db=1,
            decode_responses=True #設置爲True返回的數據格式就是時str類型
        )
    def add(self,key,data):
    	self.con.sadd(key,data) #添加
    def query(self,key):
        return self.con.smembers(key)  #拿出key對應所有值
    def delete(self,key):
        self.con.delete(key) #刪除key鍵
    def exits(self,key,data):
        return self.con.sismember(key, data)  # 判斷key裏是否有data,有則返回true

class Hashencode():
    def __init__(self):
        self.encode_machine = hashlib.md5()  # 創建一個md5對象
    def encode(self,url):
        self.encode_machine.update(url.encode()) #更新
        url_encode = self.encode_machine.hexdigest() #拿到編碼後的十六進制數據
        return url_encode

請求頭

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
                  "(KHTML, like Gecko) Chrome/73.0.3683.75 Safari/537.36",
    'accept-language': 'zh-CN,zh;q=0.9',
    'cache-control': 'max-age=0',
    'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8'
}

MySpider類

用於配置mysql服務和實現將去重後的內容存入mysql兩張表中

class MySpider():
    def __init__(self,host,user,password,db):
        # 配置信息
        db_config = {
            'host': host,  # 需要連接ip
            'port': 3306,  # 默認端口3306
            'user': user,  # 用戶名.
            'password': password,  # 用戶密碼
            'db': db,  # 進入的數據庫名.
            'charset': 'utf8'  # 編碼方式.
        }
        self.options = ChromeOptions()
        # 添加無界面參數
        self.options.add_argument('--headless')
        self.driver = Chrome(options=self.options)

        self.conn = pymysql.connect(**db_config)
        # 得到一個可執行SQL語句的光標對象
        self.cur = self.conn.cursor()  # create cursor.
        #實例化Hashencode和Redis對象
        self.en = Hashencode()
        self.red = Redis(host)
    #獲取新聞詳情頁內容
    def get_content(self,news_url,title):
        """
        獲取新聞詳情頁內容併入庫
        :param news_url: 新聞詳情頁url
        :param title: 新聞標題
        :return: 
        """
        text = requests.get(news_url, headers=headers, verify=False)
        text.encoding = 'gb2312'
        html = etree.HTML(text.text)
        content = html.xpath("//p")
        news_detail = ''
        """
        這裏的這些if判斷是爲了過濾掉一些無用的信息
        因爲每個新聞的網頁結構不同,所有沒有辦法完全精確地匹配
        這裏的過濾方法是經過測試後效果最好的。
        """
        for i in content:
            sentence = i.xpath("string()")
            if len(sentence) == 0:
                continue
            if sentence == '\n':
                continue
            if sentence.count('\t') + sentence.count('\n') + sentence.count(' ') > 7:
                continue
            if '\u4e00' <= sentence[0] <= '\u9fff' or sentence[0].isdigit() or sentence[0].isalpha():
                continue
            news_detail += sentence
        #獲取主鍵ID
        self.cur.execute('SELECT id FROM brief_news WHERE title="{}";'.format(title))
        resp = self.cur.fetchall()
        news_id = resp[0][0]
        #入庫
        self.cur.execute('INSERT INTO detail_news VALUES ("{}","{}");'.format(news_id,news_detail))
        self.conn.commit()
    #通過關鍵詞,獲取新聞標題,url,發佈時間
    def get_urls(self,keyword):
        """
        通過關鍵詞搜索新聞並提取新聞標題,url,發佈時間
        :param keyword: 關鍵詞
        :return: 
        """
        self.driver.get("http://search.people.com.cn/cnpeople/news/")
        self.driver.find_element_by_id("keyword").send_keys(keyword)
        self.driver.find_element_by_id("keyword").send_keys(Keys.ENTER)
        search_result = self.driver.page_source
        html = etree.HTML(search_result)
        news_urls = html.xpath('//div[@class="fr w800"]/ul/li/b/a/@href')
        news_titles = html.xpath('//div[@class="fr w800"]/ul/li/b/a/text()')
        update_times = re.findall("\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}",search_result)
        for i,j,k in zip(news_urls,news_titles,update_times):
            str_i = i.encode('utf-8').decode()
            str_j = j.encode('utf-8').decode()
            encode_i = self.en.encode(str_i)
            if self.red.exits("urls",encode_i):
                continue
            self.red.add("urls",encode_i)
            self.cur.execute('INSERT INTO brief_news VALUES(null,"{}","{}","{}");'.format(str_j,str_i,k))
            self.conn.commit()
            self.get_content(i,j)
        # next_page = self.driver.find_element_by_link_text("下一頁").click()
    def __del__(self):
        self.cur.close()
        self.conn.close()
        self.driver.close()

這個類實現了通過關鍵詞搜索新聞,並把第一頁新聞的標題,url,發佈時間存入第一張表,將新聞內容存入第二張表中,同時實現了url去重,不會存入同樣的數據。
關於提取第二頁的內容,讀者只需要加一個循環即可實現。

這個類在實例化時需要傳入mysql主機地址,用戶名,密碼和需要連接的數據庫名,運行時需要插入的兩個表存在,上文已經給出了創建的命令。

今天的分享就到這兒了,希望大家能夠有所收穫。

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