簡介
今天的任務是通過關鍵詞爬取人民網的新聞,並存入數據庫,同時實現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主機地址,用戶名,密碼和需要連接的數據庫名,運行時需要插入的兩個表存在,上文已經給出了創建的命令。
今天的分享就到這兒了,希望大家能夠有所收穫。