گرفتن ScreenShot از وب سایت برای سایزهای مختلف مانیتور

در این مقاله نشان می دهیم که چگونه از وب سایت screenshot گرفته و deviceها و سایز صفحه های مختلف را شبیه سازی کنیم.

گرفتن ScreenShot از وب سایت برای سایزهای مختلف مانیتور

مقدمه
یکی از نیازهای رایج اپلیکیشن های امروزی این است که بعد از وارد شدن URL توسط کاربر از صفحه screenShot تهیه نمایند. دو دلیل برای این کار وجود دارد:

- کلاینت قصد دارد از صفحه screenShot گرفته و برای مقایسه های بعدی ذخیره کند. (برای تست وب سایت و یا تثبیت یک مطلب قبل از انتشار)

- کلاینت می خواهد تصویر وب سایت را کنار یک لینک نمایش دهد.(مانند یک نمونه کار)

در نگاه اول شاید این تنها یک نیاز ساده به نظر بیاید، اما امروزه deviceهای مختلفی مانند (desktop pcها، تبلت، تلفن های همراه) را داریم و سایت های ریسپانسیو در هر یک از این ها ظاهر متفاوتی دارند و یک سطح دیگری از پیچیدگی را به وجود می آورند.

در این مقاله قصد داریم یک کتابخانه #C بنویسیم که این مشکل را حل کند. سورس کد را برای شما خواهیم گذاشت و همچنین می توانید آن را از NuGet package Web.Screenshot  دانلود و نصب نمایید.

ابتدا از این کتابخانه API استفاده می کنیم تا نحوه کار آن را به شما نشان دهیم و سپس به چگونگی پیاده سازی آن می پردازیم.

استفاده از Web.Screenshot NuGet Package:

 نصب Web.Screenshot NuGet Package

سریعترین راه برای اینکه ببینیم، web.Screenshot چگونه کار می کند ایجاد یک پروژه Consol Application می باشد. بنابراین ویژوال استودیو را باز کرده، از منوی file و New Project یک پروژه Console ایجاد می کنیم.

سپس روی Refrences راست کلیک کرده و Manage NuGet Packages... را انتخاب می کنیم.

Web.Screenshot را جستجو کرده و آن را انتخاب و نصب می کنیم.

زمانی که نصب کامل شد، لیست جدیدی از اسمبلی ها به نام Alx.Web.Screenshot به solution اضافه می شود.

استفاده از Web.Screenshot API:

کلاس اصلی یک کلاس استاتیک به نام Screenshot است که می توانیم آن را در فضای نام Alx.Web پیدا کنیم. این کلاس دو متد با چندین override ارائه می دهد.

()Take: یک اسکرین شات از صفحه سایت می گیرد و یک تصویر به عنوان خروجی برمی گرداند.

()Save: اسکرین شات های سایت را به صورت Image روی هارد دیسک ذخیره می کند.

در زیر می توانید، override متدها را مشاهده نمایید:

public static Image Take(string url, Devices device = Devices.Desktop)

public static Image Take(string url, Size size)

public static void Save(string url, string path, ImageFormat format, Devices device = Devices.Desktop)

public static void Save(string url, string path, ImageFormat format, Size size)

کلاس های Size، Image و ImageFormat کلاس های تعریف شده در Net. هستند که از طریق فضای نام System.Drawing قابل دسترسی هستند.

Devices یک enum در Alx.Web می باشد و مقادیر آن به صورت زیر است:

PhonePortrait، PhoneLandscape، TabletPortrait، TabletLandscape، Desktop

حال کد خود را در متد main متعلق به program.cs می نویسیم.

static void Main(string[] args)
{
    Console.WriteLine("Application started");
    const string url = "http://www.bbc.co.uk/news";

    var device = Devices.Desktop;
    var path = String.Format(@"C:\Temp\website-{0}.png", device);
    Alx.Web.Screenshot.Save(url, path, ImageFormat.Png, device);

    Console.WriteLine("Saved " + path);

    device = Devices.TabletLandscape;
    path = String.Format(@"C:\Temp\website-{0}.png", device);
    Alx.Web.Screenshot.Save(url, path, ImageFormat.Png, device);

    Console.WriteLine("Saved " + path);

    device = Devices.PhonePortrait;
    path = String.Format(@"C:\Temp\website-{0}.png", device);
    Alx.Web.Screenshot.Save(url, path, ImageFormat.Png, device);

    Console.WriteLine("Saved " + path);

    Console.WriteLine("Press [Enter] to exit...");
    Console.ReadLine();
}

این کد ها، 3 اسکرین شات از سایت برنامه نویسان در 3 فرمت مختلف ذخیره می کند:

- Desktop

- Tablet (landscape)

Phone(portrait) -

این فایل ها در مسیر C:\Temp ذخیره می شود، اما شما می توانید از فولدرهای متفاوتی استفاده کنید. در اینجا فایل ها را با فرمت PNG ذخیره کردیم اما می توانید از هر فرمت قابل قبولی که در System.Drawing تعریف شده استفاده کنید.

تصاویر ذخیره شده به شکل زیر می باشند:

دسکتاپ:

تبلت:

تلفن همراه:

به این ترتیب، اسکرین شات گرفتن از وب سایت ها به راحتی انجام می شود.

پیاده سازی Web.Screenshot:

حال نگاهی به پیاده سازی کد Web.Screenshot می اندازیم:

با Devices.cs شروع می کنیم.

public enum Devices
{
    [Size(375, 667)]    // Screen size for iPhone 6
    PhonePortrait,

    [Size(667, 375)]    // Screen size for iPhone 6
    PhoneLandscape,

    [Size(600, 960)]   // Screen size for Google Nexus 7
    TabletPortrait,

    [Size(960, 600)]   // Screen size for Google Nexus 7
    TabletLandscape,

    [Size(1920, 1080)]  // Screen size for Dell XPS12
    Desktop
}

همان طور که مشاهده می کنید، 5 مقدار برای سه device مختلف داریم که برخی از آنها در دو حالت (landscape و Portrait) هستند.

به صفت Size دقت نمایید که عرض و ارتفاع صفحه را برای هر Device مشخص می کند.

کد زیر مربوط به SizeAttribute.cs می باشد:

public class SizeAttribute : Attribute
{
    public int Width { get; set; }
    public int Height { get; set; }

    public Size Size
    {
        get { return new Size(Width, Height); }
    }

    public SizeAttribute(int width, int height)
    {
        Width = width;
        Height = height;
    }
}

یک extension method برای خواندن enumها ایجاد کرده ایم که در EnumExtensions.cs قرار دارد.

public static class EnumHelper
{
    public static T GetAttribute<T>(this Enum enumVal) where T : Attribute
    {
        var type = enumVal.GetType();
        var memInfo = type.GetMember(enumVal.ToString());
        var attributes = memInfo[0].GetCustomAttributes(typeof(T), false);
        return (attributes.Length > 0) ? (T)attributes[0] : null;
    }
}

و کلاس استاتیک Screenshot، متد زیر را برای خواندن سایز صفحه از مقدار enum دارد:

public static Size GetSize(Devices device)
{
    var attribute = device.GetAttribute<SizeAttribute>();

    if (attribute == null)
    {
        throw new DeviceSizeException(device);
    }

    return attribute.Size;
}

حال نگاهی به متدهای ()Take می اندازیم. توجه داشته باشید که متدهای ()Take اسکرین شات هایی از وب سایت گرفته و آن را به صورت یک object از System.Drawing.Image برمی گرداند. این کد در Screenshot.cs قرار دارد.

public static Image Take(string url, Devices device = Devices.Desktop)
{
    var size = GetSize(device);
    return Take(url, size);
}

public static Image Take(string url, Size size)
{
    var result = Capture(url, size);
    return result;
}

هر دو متد با فراخوانی ()capture که با دو آرگومان URL سایت و سایز صفحه Device، فراخوانی می شود، پایان می یابند.

حال متد ()capture را بررسی می کنیم:

private static Image Capture(string url, Size size)
{
    Image result = new Bitmap(size.Width, size.Height);

    var thread = new Thread(() =>
    {
        using (var browser = new WebBrowser())
        {
            browser.ScrollBarsEnabled = false;
            browser.AllowNavigation = true;
            browser.Navigate(url);
            browser.Width = size.Width;
            browser.Height = size.Height;
            browser.ScriptErrorsSuppressed = true;
            browser.DocumentCompleted += (sender,args) => DocumentCompleted(sender, args, ref result);

            while (browser.ReadyState != WebBrowserReadyState.Complete)
            {
                Application.DoEvents();
            }
        }
    });

    thread.SetApartmentState(ApartmentState.STA);
    thread.Start();
    thread.Join();

    return result;
}

در ابتدا توجه داشته باشید که اگر Thread جاری در single-threaded نباشد، نمی توان از کنترل های WebBrowser نمونه ای ساخت. برای اینکه مطمئن باشیم، در یک thread اجرا می شود، یک Thread جدید می سازیم و state آن را به صورت ApartmentState.STA تنظیم می کنیم.

خاصیت های زیر را در قسمت Properties برای این کنترل ها در نظر می گیریم:

- پیمایشگر صفحه را غیرفعال می کنیم.

- عرض و ارتفاع را تنظیم می کنیم.

- script error ها را متوقف می نماییم.

برای هدایت به صفحه درخواست شده،  Navigate(url) را فراخوانی می کنیم.

رویداد DocumentCompeleted را برای زمانی که صفحه بارگیری می شود و Document به صورت ready در می آید، تنظیم کرده ایم که اجرا شود.

سپس حلقه While و پردازش رویدادها را تا زمانی که حالت مرورگر روی Compelete ست شود، قرار می دهیم.

در نهایت، Thread را استارت می کنیم ()Thread.Start و منتظر می مانیم تا کامل شود، ()Thread.Join و سپس نتیجه را برمی گردانیم که تصویری با یکی از فرمت های گفته شده است. Thread زمانی به پایان می رسد که حالت مرورگر روی Compelete ست شود. رویداد DocumentCompeleted تا این لحظه اجرا می شود. کد زیر مربوط به handler می باشد:

private static void DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e, ref Image image)
{
    var browser = sender as WebBrowser;

    if (browser == null) throw new Exception("Sender should be browser");
    if (browser.Document == null) throw new Exception("Document is missing");
    if (browser.Document.Body == null) throw new Exception("Body is missing");

    using (var bitmap = new Bitmap(browser.Width, browser.Height))
    {
        browser.DrawToBitmap(bitmap, new Rectangle(0, 0, browser.Width, browser.Height));
        image = (Image)bitmap.Clone();
    }
}

در ابتدا صحت برنامه را چک کرده و سپس یک Bitmap به همان سایز مرورگر ایجاد می کنیم. برای کپی محتوای مرورگر به صورت عکس در متغیر Bitmap، متد ()browser.DrawToBitmap را فراخوانی می کنیم. سپس متغیر image (که به وسیله refrence ارسال می شود) با یک clone از Bitmap ست می کنیم(چرا که Bitmap پس از خروج از بلاک مربوط به آن، از بین می رود و از این رو، برای متد فراخواننده آن قابل دسترس نخواهد بود)

تا اینجا توانستیم، اسکرین شاتی از صفحه تهیه کرده و آن را در یک شی از نوع Image ذخیره کنیم. حال نگاهی به متدهای ()Save می اندازیم:

public static void Save(string url, string path, ImageFormat format, Devices device = Devices.Desktop)
{
    var size = GetSize(device);
    Save(url, path, format, size);
}

public static void Save(string url, string path, ImageFormat format, Size size)
{
    var image = Take(url, size);

    using (var stream = new MemoryStream())
    {
        image.Save(stream, format);
        var bytes = stream.ToArray();
        File.WriteAllBytes(path, bytes);
    }
}

متدهای ()Save به راحتی متدهای ()Take را فراخوانی کرده و شی Image را به فرمت خواسته شده روی دیسک ذخیره می کنند.

روش های دیگر:

Selenium WebDriver:

Selenium WebDriver امکانات بسیار بیشتری از یک اسکرین شات گرفتن ساده ارائه می دهد. زمانی که صحفه بارگیری می شود، می تواند با شبیه سازی کلیک روی لینک به آدرس دیگری هدایت شود. همچنین می تواند به راحتی محتوای صفحه را بخواند که این می تواند برای پر کردن فرم ها بسیار مفید باشد. و همچنین می تواند، با ایجاد درایورهای مختلف مرورگرهای متفاوتی مانند Firefox و Chrome را شبیه سازی کند.

PhantomJS:

PhantomJS کتابخانه دیگری است که می توان از آن استفاده کرد. PhantomJS یک مرورگر بدون Head است و عملکرد آن بسیار شبیه Selenium.WebDriver. PhantomJS است که می تواند با جاوااسکریپت نوشته شود.

اما چرا از آنها استفاده نمی کنیم؟

دلیل اینکه کتابخانه Web.Screenshot ایجاد کردیم، این است که هر دو کتابخانه ای که در بالا گفته شد زمانی که در وب سایت های اینترانتی با استفاده از احراز هویت ویندوز با پروتکل Kerboros به کار گرفته می شوند، چندان قابل اعتماد نیستند.

آموزش سی شارپ

فایل های ضمیمه