Bir Portal Sayfasını Parse Edip Tüm Clientlara SignalR ve AngularJs İle Real Time Gösterme
Selamlar;
Bugün http://www.msn.com/tr-tr/ haber portalından aşağıda görülen 4’lü manşet haberleri parse edilip, azure üzerinde bir database’e kaydedilecek. Daha sonra Mvc ve AngularJs ile yazılacak bir web sayfasında gösterilecek. Belli zaman aralıkları ile ilgili portal kontrol edilip, manşet haberlerinde değişiklik olup olmadığına bakılacak. Herhangi bir değişiklik durumunda , yeni gelen haberler database’e kaydedilip signalR ile tüm clientlara ilgili data ile birlikte real time olarak push edilecek. Haberlerin gösterileceği sayfa için, konulduğu yerde iframe basan bir script yazılacak. Böylece isteyen herkez kendi sayfalarında parse edilen haberleri kolaylıkla gösterebilecek. Aynı zamanda yeni haber geldiği zamanda sayfayı refresh edilmeden ilgili haberler signalR ile real time olarak yenilenebilecek.
Öncelikle Interval’ı: 30000 olan bir timer ekleyelim.
Aşağıda timer ile ilgili kodlama gözükmektedir. Belli zaman aralıkları ile GetNews() methodu ile ilgili portal pars edilip manşet haberler çekilmektedir.
Windows Application Form1.cs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { GetNews(); timer1.Start(); } protected override void OnVisibleChanged(EventArgs e) { base.OnVisibleChanged(e); this.Visible = false; } private void timer1_Tick(object sender, EventArgs e) { timer1.Stop(); GetNews(); timer1.Start(); } |
Şimdi istenen siteyi pars etmek için aşağıda görülen Watin package indirilir. Watin’in esas amacı sitelere load test yapmaktır. Explorer bir browser’ı açarak belirlenen işlem adımlarını siteye uygular. Ama amaca yönelik olarak bu örnekde de olduğu gibi pars işlemi içinde kullanılabilir:)
Msn.com.tr’nin aşağıdaki manşet haber kodlarını 30sn’de bir parse eden windows application’ı yazalım.
Aşağıda görüldüğü gibi Watin için gerekli setler yapıldıktan sonra (ListItem)browser.Lists.Filter(Find.ByClass(“flexbox-polyfill”))[0].ListItems[0].NextSibling altındaki sıralı 4 ListItem tek tek alınıp, CodeFirst ile oluşturulmuş tblNews modeli doldurulur ve List<tblNews> dizisine eklenir. tblNews tablosu ve ilgili DbContext aşağıda görüldüğü gibidir.
DAL/tblNews.cs&MsnContext.cs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
namespace DAL { using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Data.Entity.Spatial; public partial class tblNews { public int ID { get; set; } [StringLength(500)] public string Title { get; set; } [StringLength(500)] public string ImageUrl { get; set; } [StringLength(500)] public string Link { get; set; } public DateTime CreatedDate { get; set; } } } namespace DAL { using System; using System.Data.Entity; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; public partial class MsnContext : DbContext { public MsnContext() : base("name=MsnContext") { } public virtual DbSet<tblNews> tblNews { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { } } } |
GetNews() methodu ile çekilen haberler eğer cacheDatas boş ise doldurulur. Değil ise isDataChange() methodu ile haber resimleri karşılaştırılarak yeni gelen haberin olup olmadığına bakılır. Var ise tablo temizlenip yeni haberler kaydedilir. Ve signalR Hub GetNews class’ının ReloadNews() methodu çağrılarak tüm clientlara ilgili data push edilir.
Windows Application Form1.cs(Devam):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
public async void GetNews() { Settings.AttachToBrowserTimeOut = 240; //240 seconds Settings.WaitUntilExistsTimeOut = 240; Settings.WaitForCompleteTimeOut = 240; Settings.Instance.MakeNewIeInstanceVisible = false; Settings.AutoCloseDialogs = true; string url = "http://www.msn.com/tr-tr/"; try { List<tblNews> datas; using (var browser = new IE(url)) { datas = new List<tblNews>(); browser.WaitForComplete(); using (MsnContext dbContext = new MsnContext()) { ListItem News = (ListItem)browser.Div(Find.ByClass("todaystripe grid loaded layout-large")).Lists[0].ListItems[0].NextSibling; for (int i = 0; i < 4; i++) { tblNews model = new tblNews(); model.CreatedDate = DateTime.Now; model.Link = News.Links[0].Url; model.Title = News.Links[0].Spans[0].Text; model.ImageUrl = News.Links[0].Images[0].Src; datas.Add(model); News = (ListItem)News.NextSibling; } } } bool isChange = false; cacheDatas = null;//Tekrardan database silinince yenilenmesi için koyduk. if (cacheDatas == null) { FillCache(); } foreach (tblNews item in datas) { if (!isDataChange(item)) { isChange = true; } } if (isChange) { FillCache(); using (MsnContext dbContext = new MsnContext()) { dbContext.Database.ExecuteSqlCommand("TRUNCATE TABLE tblNews"); dbContext.tblNews.AddRange(((IEnumerable<tblNews>)datas)); dbContext.SaveChanges(); } //Triger SignalR HubConnection hubConnection = new HubConnection("http://MsnBanner.azurewebsites.net/"); IHubProxy hubProxy = hubConnection.CreateHubProxy("GetNews"); await hubConnection.Start(new LongPollingTransport()); hubProxy.Invoke("ReloadNews"); } } catch (Exception ex) { } } public List<tblNews> cacheDatas; public void FillCache() { if (cacheDatas == null) { cacheDatas = new List<tblNews>(); } else { cacheDatas.Clear(); } using (HurriyetContext dbContext = new HurriyetContext()) { cacheDatas = dbContext.tblNews.ToList(); } } public bool isDataChange(tblNews data) { return cacheDatas.Any(cd => cd.ImageUrl == data.ImageUrl); } |
Çekilen haberlerin gösterileceği Mvc projesi için Nuget’den indirilecek packagelar aşağıdaki gibidir.
HomeController.cs: Mvc projesnin HomeController ve SignalR Hub class’ı kodları aşağıdaki gibidir. Sayfa ilk yüklendiğinde Index() actionına hiç bir model gönderilmemiştir. GetNews Hub class’ınına OnConnected() methodunda client bağlandığı zaman, ilgili haberler databaseden çekilerek kullanıcının getAllNews() function’ına parametre olarak gönderilir. Ayrıca Windows Application, timer sayesinde msn portalını her 30sn de bir parse ederek yeni bir haber girişinin olup olmadığını kontrol eder. Yeni haber girişi olduğunda, ilgili haberleri database’e kaydedip tüm clientlara push etmek için alttaki GetNews Hub class’ına ait ReloadNews() methodu tetiklenir. ReloadNews() methodu da tüm clientlardaki reloadNews() function’ına ilgili datayı push ederek yeni haberlerin real time olarak gözükmesini sağlar.
HomeController.cs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
using DAL; using Microsoft.AspNet.SignalR; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Web; using System.Web.Mvc; namespace MsnBanner.Controllers { public class HomeController : Controller { public ActionResult Index() { return View(); } } public class GetNews : Hub { public override async Task OnConnected() { List<tblNews> newsList = new List<tblNews>(); using (MsnContext dbContext = new MsnContext()) { foreach (var item in dbContext.tblNews.ToList()) { tblNews model = new tblNews(); model.CreatedDate = item.CreatedDate; model.ID = item.ID; model.ImageUrl = item.ImageUrl; model.Link = item.Link; model.Title = item.Title; newsList.Add(model); } } await Clients.Caller.getAllNews(newsList); } public void ReloadNews() { List<tblNews> newsList = new List<tblNews>(); using (MsnContext dbContext = new MsnContext()) { foreach (var item in dbContext.tblNews.ToList()) { tblNews model = new tblNews(); model.CreatedDate = item.CreatedDate; model.ID = item.ID; model.ImageUrl = item.ImageUrl; model.Link = item.Link; model.Title = item.Title; newsList.Add(model); } } Clients.All.reloadNews(newsList); } } } |
Şimdi de gelelim haberlerin gösterileceği Index.cshtml sayfasına. MVVM design pattern’ı ile AngularJS javascript kütüphanesi kullanılarak aşağıdaki gibi yazılmıştır. Hub connect olduğu zaman client tarafındaki getAllNews() function’ı, çekilen haber datası ile tetiklenir. Toplam kayıt sayısı mod %2’ye göre dizilip $scope.datas’a 2 li dizi olarak atanır. reloadNews() function’ı msn’de manşet haber değiştiğinde, tüm clientların bundan haberdar olması için tetiklenir. Yeni haber dataları $scope.datas‘a atandıktan sonra ilgili data $scope.$apply() ile yenilenir.
Index.cshtml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
@{ Layout = null; } <!DOCTYPE html> <html> <head> <script src="~/Scripts/angular.min.js"></script> <script src="~/Scripts/jquery-2.1.3.min.js"></script> <script src="~/Scripts/jquery.signalR-2.2.0.min.js"></script> <script src="~/signalr/hubs"></script> <script> (function (angular) { function Controller($scope) { var hubProxy = $.connection.getNews; hubProxy.client.getAllNews = function (newsList) { console.log("client.getNews (" + JSON.stringify(newsList) + ")"); $scope.datas = []; dataSet = []; for (var i = 0; i < newsList.length; i++) { if (i % 2 == false) { $scope.datas.push(dataSet); dataSet = []; } dataSet.push(newsList[i]); } if (dataSet.length) { $scope.datas.push(dataSet); dataSet = []; } $scope.$apply(); } hubProxy.client.reloadNews = function (newsList) { console.log("client.reloadImages (" + JSON.stringify(newsList) + ")"); $scope.datas = []; dataSet = []; for (var i = 0; i < newsList.length; i++) { if (i % 2 == false) { $scope.datas.push(dataSet); dataSet = []; } dataSet.push(newsList[i]); } if (dataSet.length) { $scope.datas.push(dataSet); dataSet = []; } $scope.$apply(); } $.connection.hub.logging = true; $.connection.hub.start().done(function () { console.log("hub.start.done"); }).fail(function (error) { console.log(error); }); } angular.module("app", []).controller("Controller", ["$scope", Controller]); })(angular) </script> <meta name="viewport" content="width=device-width" /> <title>Index</title> </head> <body> <div ng-app="app" ng-controller="Controller"> <table> <tr ng-repeat="news in datas"> <td ng-repeat="new in news" style="padding:2px"> <a href="{{new.Link}}" target="_blank" style="text-decoration:none"> <textarea style="width:210px;height:35px;border:none;background-color:transparent;font-size:14px;color:#454545;font-family:'pt_sansbold';font-weight: bold">{{new.Title}}</textarea> <div><img ng-src="{{new.ImageUrl}}" alt="{{new.Title}}" style="width:150px;height:125px; border:6px solid lightgray"/></div> </a> </td> </tr> </table> </div> </body> </html> |
Yukarıda görüldüğü gibi ng-repeat ile table’a ilgili data iç içe 2 döngü ile 2’li sıra şeklinde sıralanır. Table içine haberin başlığı, tıklanınca gidilecek haber detay url’i ve haber resmi konulmaktadır.
Tüm clientlara verilecek, haberleri iframe içine basıcak javascript dosyasının kodları aşağıdaki gibidir.
MsnBanner.js:
1 |
document.write("<iframe src='http://msnbanner.azurewebsites.net/' width='481' height='406' marginheight='0' align='top' scrolling='No' frameborder='0' hspace='0' vspace='0'></iframe>"); |
Yukarıdaki örnekden de anlaşılacağı gibi web üzerinde çeşitli portallardan istenen karma bilgileri toplamak ve bunları real time olarak clientlarla paylaşmak hem çok kolay hem de çok zevklidir.
Yeni bir makale görüşmek üzere hoşçakalın.
Bora hocam elinize sağlık. Yine güzel ve farklı bir konu ile ilgili paylaşımda bulunmuşsunuz. Paylaşımlarınızın devamını bekliyoruz.
Teşekkürler Mesut.
Selamlar Hocam;
Çok güzel bir yazı olmuş. Gerçekten hep hayal ederdim siz de yapmışınız:)
Elinize sağlık. İyi çalışmalar.
Teşekkürler Ceyda.
Selamlar Bora Hocam;
Tek kelime ile muhteşem bir yazı olmuş..Bu yazı özgün değil ise daha ne özgündür:)
Umarım birgün ben de sizin gibi yazabilirim….Yarısı yazsam da olur:)
Gene tebrikler.
Teşekkürler Veli.
Yarısı değil 2 katı yazarsın. Yeter ki araştır. Artık imkanlar 1990’lardaki gibi değil. İstediğin bilgiye istediğin an ulaşabilirsin. Çağ bilgi çağ…
İyi çalışmalar.