摘要:一項目背景我本人所在公司是一個國有企業,自建有較大的內部網絡門戶群,幾乎所有部門發布各類通知工作要求等消息都在門戶網站進行。為了改變這種狀況,就想建立一個內部網絡消息跟蹤通知系統。
一、項目背景
我本人所在公司是一個國有企業,自建有較大的內部網絡門戶群,幾乎所有部門發布各類通知、工作要求等消息都在門戶網站進行。由于對應的上級部門比較多,各類通知通告、領導講話等內容類目繁多,要看一遍真需要花費點時間。更重要的是有些會議通知等時效性比較強的消息一旦遺漏錯過重要會議就比較麻煩。為了改變這種狀況,就想建立一個內部網絡消息跟蹤、通知系統。
二、基本功能 主要功能:系統功能比較簡單,主要就是爬取內部網絡固定的一些通知頁面,發現新的通知就向指定的人發送通知郵件。
涉及到的功能點:
1.常規頁面請求
2.post請求
3.數據存儲
4.識別新消息
5.郵件通知
6.定時啟動,循環運行
上圖顯示了完成狀態的文件結構,與新建的scrapy項目相比增加的文件有兩部分:
一是spiders目錄下的6個爬蟲文件,對應了6個欄目,以后根據需要還會再增加;
二是涉及定時啟動、循環運行功能的幾個文件,分別是commands文件夾、noticeStart.py、setup.py、autorun.bat
import scrapy class JlshNoticeItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() noticeType = scrapy.Field() #通知類別 noticeTitle = scrapy.Field() #通知標題 noticeDate = scrapy.Field() #通知日期 noticeUrl = scrapy.Field() #通知URL noticeContent = scrapy.Field() #通知內容2. spider
篇幅關系,這里只拿一個爬蟲做例子,其它的爬蟲只是名稱和start_url不同,下面代碼盡量做到逐句注釋。
代碼from scrapy import Request from scrapy import FormRequest from scrapy.spiders import Spider from jlsh_notice.items import JlshNoticeItem from jlsh_notice.settings import DOWNLOAD_DELAY from scrapy.crawler import CrawlerProcess from datetime import date import requests import lxml import random import re #======================================================= class jlsh_notice_spider_gongsitongzhi(Spider): #爬蟲名稱 name = "jlsh_gongsitongzhi" start_urls = [ "http://www.jlsh.petrochina/sites/jlsh/news/inform/Pages/default.aspx", #公司通知 ] #======================================================= #處理函數 def parse(self, response): noticeList = response.xpath("http://ul[@class="w_newslistpage_list"]//li") #======================================================= #創建item實例 item = JlshNoticeItem() for i, notice in enumerate(noticeList): item["noticeType"] = "公司通知" item["noticeTitle"] = notice.xpath(".//a/@title").extract()[0] item["noticeUrl"] = notice.xpath(".//a/@href").extract()[0] #======================================================= dateItem = notice.xpath(".//span[2]/text()").extract()[0] pattern = re.compile(r"d+") datetime = pattern.findall(dateItem) yy = int(datetime[0])+2000 mm = int(datetime[1]) dd = int(datetime[2]) item["noticeDate"] = date(yy,mm,dd) #======================================================= content_html = requests.get(item["noticeUrl"]).text content_lxml = lxml.etree.HTML(content_html) content_table = content_lxml.xpath( "http://div[@id="contentText"]/div[2]/div | //div[@id="contentText"]/div[2]/p") item["noticeContent"] = [] for j, p in enumerate(content_table): p = p.xpath("string(.)") #print("p:::::",p) p = p.replace("xa0"," ") p = p.replace("u3000", " ") item["noticeContent"].append(p) yield item #======================================================= pages = response.xpath("http://div[@class="w_newslistpage_pager"]//span") nextPage = 0 for i, page_tag in enumerate(pages): page = page_tag.xpath("./a/text()").extract()[0] page_url = page_tag.xpath("./a/@href").extract() if page == "下一頁>>": pattern = re.compile(r"d+") page_url = page_url[0] nextPage = int(pattern.search(page_url).group(0)) break #======================================================= if nextPage > 0 : postUrl = self.start_urls[0] formdata = { "MSOWebPartPage_PostbackSource":"", "MSOTlPn_SelectedWpId":"", "MSOTlPn_View":"0", "MSOTlPn_ShowSettings":"False", "MSOGallery_SelectedLibrary":"", "MSOGallery_FilterString":"", "MSOTlPn_Button":"none", "__EVENTTARGET":"", "__EVENTARGUMENT":"", "__REQUESTDIGEST":"", "MSOSPWebPartManager_DisplayModeName":"Browse", "MSOSPWebPartManager_ExitingDesignMode":"false", "MSOWebPartPage_Shared":"", "MSOLayout_LayoutChanges":"", "MSOLayout_InDesignMode":"", "_wpSelected":"", "_wzSelected":"", "MSOSPWebPartManager_OldDisplayModeName":"Browse", "MSOSPWebPartManager_StartWebPartEditingName":"false", "MSOSPWebPartManager_EndWebPartEditing":"false", "_maintainWorkspaceScrollPosition":"0", "__LASTFOCUS":"", "__VIEWSTATE":"", "__VIEWSTATEGENERATOR":"15781244", "query":"", "database":"GFHGXS-GFJLSH", "sUsername":"", "sAdmin":"", "sRoles":"", "activepage":str(nextPage), "__spDummyText1":"", "__spDummyText2":"", "_wpcmWpid":"", "wpcmVal":"", } yield FormRequest(postUrl,formdata=formdata, callback=self.parse)說明,以下說明要配合上面源碼來看,不多帶帶標注了
start_urls #要爬取的頁面地址,由于各個爬蟲要爬取的頁面規則略有差異,所以做了6個爬蟲,而不是在一個爬蟲中寫入6個URL。通過查看scrapy源碼,我們能夠看到,start_urls中的地址會傳給一個內件函數start_request(這個函數可以根據自己需要進行重寫),start_request向這個start_urls發送請求以后,所得到的response會直接轉到下面parse函數處理。
xpath ,下圖是頁面源碼:通過xpath獲取到response中class類是"w_newslistpage_list"的ul標簽下的所有li標簽,這里所得到的就是通知的列表,接下來我們在這個列表中做循環。
先看下li標簽內的結構:notice.xpath(".//a/@title").extract()[0] #獲取li標簽內a標簽中的title屬性內容,這里就是通知標題
notice.xpath(".//a/@href").extract()[0] #獲取li標簽內a標簽中的href屬性內容,這里就是通知鏈接
notice.xpath(".//span[2]/text()").extract()[0] #獲取li標簽內第二個span標簽中的內容,這里是通知發布的日期
接下來幾行就是利用正則表達式講日期中的年、月、日三組數字提取出來,在轉換為日期類型存入item中。
再下一段,是獲得通知內容,這里其實有兩種方案,一個是用scrapy的request發送給內部爬蟲引擎,得到另外一個response后再進行處理,另一種就是我現在這樣直接去請求頁面。由于內容頁面比較簡單,只要獲得html代碼即可,所以就不麻煩scrapy處理了。
request.get得到請求頁面的html代碼
利用lxml庫的etree方法格式化html為xml結構
利用xpath獲取到div[@id="contentText"]內所有p標簽、div標簽節點。(可以得到99%以上的頁面內容)
所得到的所有節點將是一個list類型數據,所有我們做一個for in循環
p.xpath("string(.)") 是獲取到p標簽或div標簽中的所有文本,而無視其他html標簽。
用replace替換到頁面中的半角、全角空格(xa0、u3000)
每得到一行清洗過的數據,就將其存入item["noticeContent"]中
最后將item輸出
在scrapy中,yield item后,item會提交給scrapy引擎,再又引擎發送給pipeline處理。pipeline一會再說。
接下來的代碼就是處理翻頁。這里的頁面翻頁是利用js提交請求完成的,提交請求后,會response一個新的頁面列表首先利用xpath找到頁面導航欄的節點,在獲取到的所有節點中做for in循環,直到找到帶有“下一頁”的節點,這里有下一頁的頁碼,還是利用正則表達式來得到它,并將其轉為int類型。
yield FormRequest(postUrl,formdata=formdata, callback=self.parse)
利用scrpay自帶的FormRequest發送post請求,這里的formdata是跟蹤post請求時得到的,要根據自己的網站調整,callback指示講得到的response反饋給parse函數處理(也就是新的一頁列表)
到此為止,就是spider文件的所有,這個文件唯一對外的輸出就是item,它會有scrapy引擎轉給pipeline處理3. pipeline 代碼
from scrapy import signals from scrapy.contrib.exporter import CsvItemExporter from jlsh_notice import settings import pymysql import time import smtplib from email.mime.text import MIMEText from email.utils import formataddr class JlshNoticePipeline(object): def process_item(self, item, spider): return item # 用于數據庫存儲 class MySQLPipeline(object): def process_item(self, item, spider): #======================================================= self.connect = pymysql.connect( host=settings.MYSQL_HOST, port=3306, db=settings.MYSQL_DBNAME, user=settings.MYSQL_USER, passwd=settings.MYSQL_PASSWD, charset="utf8", use_unicode=True) # 通過cursor執行增刪查改 self.cursor = self.connect.cursor() #======================================================= # 查重處理 self.cursor.execute( """select * from jlsh_weblist where noticeType = %s and noticeTitle = %s and noticeDate = %s """, (item["noticeType"], item["noticeTitle"], item["noticeDate"])) # 是否有重復數據 repetition = self.cursor.fetchone() #======================================================= # 重復 if repetition: print("===== Pipelines.MySQLPipeline ===== 數據重復,跳過,繼續執行..... =====") else: # 插入數據 content_html = "" for p in item["noticeContent"]: content_html = content_html + "說明" + p + "
" self.cursor.execute( """insert into jlsh_weblist(noticeType, noticeTitle, noticeDate, noticeUrl, noticeContent, record_time) value (%s, %s, %s, %s, %s, %s)""", (item["noticeType"], item["noticeTitle"], item["noticeDate"], item["noticeUrl"], content_html, time.localtime(time.time()))) try: # 提交sql語句 self.connect.commit() print("===== Insert Success ! =====", item["noticeType"], item["noticeTitle"], item["noticeDate"], item["noticeUrl"]) except Exception as error: # 出現錯誤時打印錯誤日志 print("===== Insert error: %s ====="%error) #======================================================= #定向發送郵件 if settings.SEND_MAIL: sender="***@***.com" # 發件人郵箱賬號 password = "********" # 發件人郵箱密碼 receiver="*****@*****.com" # 收件人郵箱賬號,我這邊發送給自己 title = item["noticeTitle"] content = """%s
%s
%s """ % (item["noticeType"], item["noticeUrl"], item["noticeTitle"], item["noticeDate"], content_html) ret=self.sendMail(sender, password, receiver, title, content) if ret: print("郵件發送成功") else: print("郵件發送失敗") pass self.connect.close() return item #======================================================= def sendMail(self, sender, password, receiver, title, content): ret=True try: msg=MIMEText(content,"html","utf-8") msg["From"]=formataddr(["", sender]) # 括號里的對應發件人郵箱昵稱、發件人郵箱賬號 msg["To"]=formataddr(["",receiver]) # 括號里的對應收件人郵箱昵稱、收件人郵箱賬號 msg["Subject"]="郵件的主題 " + title # 郵件的主題,也可以說是標題 server=smtplib.SMTP("smtp.*****.***", 25) # 發件人郵箱中的SMTP服務器,端口是25 server.login(sender, password) # 括號中對應的是發件人郵箱賬號、郵箱密碼 server.sendmail(sender,[receiver,],msg.as_string()) # 括號中對應的是發件人郵箱賬號、收件人郵箱賬號、發送郵件 server.quit() # 關閉連接 except Exception: # 如果 try 中的語句沒有執行,則會執行下面的 ret=False ret=False return ret #=======================================================
這里的pipeline是我自己建立的,寫好后在setting中改一下設置就可以了。因為scrapy的去重機制只針對爬蟲一次運行過程有效,多次循環爬取就不行了,所以為了實現不爬取重復數據,使用mysql就是比較靠譜的選擇了。
pymysql是python鏈接mysql的包,沒有的話pip安裝即可。
首先建立一個pymysql.connect實例,將連接mysql的幾個參數寫進去,我這里是先寫到setting里面再導入,也可以直接寫,值得注意的是port參數(默認是3306)不要加引號,因為它必須是int類型的。
接下來建立一個cursor實例,用于對數據表進行增刪查改。
cursor.execute() 方法是定義要執行的sql命令,這里注意就是execute只是定義,不是執行。
cursor.fetchone() 方法是執行sql并返回成功與否的結果。這里進行了數據查詢,如果能夠查到,說明這條記錄已經建立,如果沒有,就可以新增數據了。
由mysql數據庫不接受list類型的數據,所以接下來要對item["noticeContent"]做一下處理(他是list類型的,還記得么^_^)。在item["noticeContent"]中做for in循環,把他的每一項內容用標簽包起來,組成一個長字符串。
接下來還是寫sql命令:insert into .....
寫完以后用connect.commit()提交執行
最后就是發送郵件了,自建一個sendMail函數,發送郵件用到了兩個python包:smtplib 和 email,具體沒啥說的,照著寫就行了,我是一次成功。。
到此為止,就可以運行爬蟲了,可以看到數據庫中已經有了爬取的內容。。。4. settings.py
注冊pipeline
ITEM_PIPELINES = { "jlsh_notice.pipelines.MySQLPipeline": 300, }
log輸出的定義,四個任選其一
LOG_LEVEL = "INFO" LOG_LEVEL = "DEBUG" LOG_LEVEL = "WARNING" LOG_LEVEL = "CRITICAL"
關于爬蟲終止條件的定義,默認不設置
#在指定時間過后,就終止爬蟲程序. CLOSESPIDER_TIMEOUT = 60 #抓取了指定數目的Item之后,就終止爬蟲程序. CLOSESPIDER_ITEMCOUNT = 10 #在收到了指定數目的響應之后,就終止爬蟲程序. CLOSESPIDER_PAGECOUNT = 100 #在發生了指定數目的錯誤之后,就終止爬蟲程序. CLOSESPIDER_ERRORCOUNT = 1005. 實現自動執行 (1) 同時執行多個爬蟲。
首先,在項目目錄下建立新的目錄(與spider目錄同級),名為“commands”,內建兩個文件:
__init__.py (空文件,但是要有)
crawlall.py
from scrapy.commands import ScrapyCommand from scrapy.utils.project import get_project_settings class Command(ScrapyCommand): requires_project = True def syntax(self): return "[options]" def short_desc(self): return "Runs all of the spiders" def run(self, args, opts): spider_list = self.crawler_process.spiders.list() for name in spider_list: self.crawler_process.crawl(name, **opts.__dict__) self.crawler_process.start()
然后在項目目錄下建立一個setup.py文件
from setuptools import setup, find_packages setup(name="scrapy-mymodule", entry_points={ "scrapy.commands": [ "crawlall=jlsh_notice.commands:crawlall", ], }, )
這個時候,在scrapy項目目錄下執行scrapy crawlall即可運行所有的爬蟲(2) 每隔一段時間運行爬蟲。
在項目目錄下新建一個noticeStart.py文件(名稱任意),利用python中的os和time包實現每隔一段時間運行一個命令。
import time import os while True: os.system("scrapy crawlall") remindTime = 5 remindCount = 0 sleepTime = 60 while remindCount * remindTime < sleepTime: time.sleep(remindTime*60) remindCount = remindCount + 1 print("已等待%s分鐘,距離下一次搜集數據還有%s分鐘......"%(remindCount*remindTime,(sleepTime/remindTime-(remindCount))*remindTime))(3) 實現開機運行。
首先:由于cmd命令打開目錄在c盤,我的scrapy項目在e盤,所以要做一個bat文件跳轉目錄并運行py文件
autorun.bat
e: cd e:PythonProjectsScrapyProjectsjlsh_noticejlsh_notice python noticeStart.py
其次:打開計劃任務程序,創建基本任務,運行程序選擇剛剛的bat文件,值得說明的是,計劃任務觸發器不要設置啟動后立即執行,不然可能會失敗,要延遲1分鐘運行。
到此為止,所有的代碼完成,以后還要根據實際情況增加更多的通知類別,也可以根據不同領導的關注點不同,分別發送郵件提醒。歡迎有興趣的朋友留言交流。。。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/42115.html
摘要:支持一鍵部署項目到集群。添加郵箱帳號設置郵件工作時間和基本觸發器,以下示例代表每隔小時或當某一任務完成時,并且當前時間是工作日的點,點和點,將會發送通知郵件。除了基本觸發器,還提供了多種觸發器用于處理不同類型的,包括和等。 showImg(https://segmentfault.com/img/remote/1460000018772067?w=1680&h=869); 安裝和配置 ...
摘要:以上示例代表當發現條或條以上的級別的時,自動停止當前任務,如果當前時間在郵件工作時間內,則同時發送通知郵件。 showImg(https://segmentfault.com/img/remote/1460000018052810); 一、需求分析 初級用戶: 只有一臺開發主機 能夠通過 Scrapyd-client 打包和部署 Scrapy 爬蟲項目,以及通過 Scrapyd JS...
摘要:健全的告警分析體系真正認識你的團隊好的告警分析機制能夠幫助管理者分析團隊整體的工作情況,根據作為評判標準。根據告警內容分析也是很有必要的,能夠幫助團隊管理者對資源進行適當的調整,工作重心的調整。 「路漫漫其修遠兮,吾將上下而求索」,「轉身」不見得華麗,但我必須「轉身」,不要安逸于現在的運維狀況。 如果你運維一線人員,是否會遇到以下情況: 公司所有的服務器告警消息會塞滿自己的整個郵箱,...
閱讀 2388·2021-11-24 10:31
閱讀 3440·2021-11-23 09:51
閱讀 2254·2021-11-15 18:11
閱讀 2402·2021-09-02 15:15
閱讀 2464·2019-08-29 17:02
閱讀 2296·2019-08-29 15:04
閱讀 845·2019-08-29 12:27
閱讀 2869·2019-08-28 18:15