نحن نحلل أي موقع في غضون ثوان. كيفية الحصول على المعلومات الضرورية من الموقع باستخدام Selenium و XPath و Proxy Sever

داروفا ، هبر! منذ حوالي عام قررت جني الأموال من المراهنات الرياضية باستخدام معرفتي بالرياضيات والبرمجة ، ثم عثرت على مشكلة صغيرة - كيف أحصل على المعلومات التي أحتاجها من الموقع؟ كيفية تحليل صفحات الويب؟ في هذه المقالة سأخبر بكلمات بسيطة عن التفاصيل الدقيقة التي تعلمتها.







تفسير



ما هو الاعراب؟ هذا هو جمع وتنظيم المعلومات التي يتم نشرها على مواقع الويب باستخدام برامج خاصة تعمل على أتمتة العملية.



يستخدم الإعراب عادة لتحليل التسعير واسترجاع المحتوى.



بداية



لجمع الأموال من وكلاء المراهنات ، كان علي أن أتلقى على الفور معلومات حول احتمالات أحداث معينة من عدة مواقع. لن ندخل في الجزء الرياضي.



منذ أن درست لغة C # في شرقا ، قررت أن أكتب كل شيء فيها. نصح الرجال في Stack Overflow باستخدام Selenium WebDriver. إنه برنامج تشغيل متصفح (مكتبة برامج) يسمح لك بتطوير البرامج التي تتحكم في سلوك المتصفح. اعتقدت أن هذا ما نحتاجه.



قمت بتثبيت المكتبة وركضت لمشاهدة الأدلة على الإنترنت. بعد فترة ، كتبت برنامجًا يمكنه فتح متصفح ومتابعة بعض الروابط.

الصيحة! بالرغم من التوقف ، كيف تضغط على الأزرار ، كيف تحصل على المعلومات اللازمة؟ سيساعدنا XPath هنا.



XPath



بعبارات بسيطة ، إنها لغة للاستعلام عن عناصر مستندات XML و XHTML.



بالنسبة لهذه المقالة ، سأستخدم Google Chrome. ومع ذلك ، يجب أن تحتوي المتصفحات الحديثة الأخرى على واجهة مشابهة جدًا ، إن لم تكن متشابهة.



لرؤية رمز الصفحة التي تتصفحها ، اضغط على F12.







لمعرفة مكان وجود عنصر في الصفحة (نص ، صورة ، زر) في التعليمات البرمجية ، انقر فوق السهم في الزاوية اليسرى العليا وحدد هذا العنصر في الصفحة. الآن دعنا ننتقل إلى بناء الجملة.



بناء الجملة القياسي لكتابة XPath:



// tagname [@ attribute = 'value']



// : يحدد كل العقد في مستند html بدءًا من العقدة الحالية

Tagname : Tag الخاص بالعقدة الحالية.

@ : تحديد السمات

السمة : اسم سمة العقدة.

القيمة : قيمة السمة.



قد لا يكون الأمر واضحًا في البداية ، ولكن بعد الأمثلة يجب أن يكون كل شيء في مكانه الصحيح.



لنلقِ نظرة على بعض الأمثلة البسيطة:



// input [@ type = 'text']



// label [@ id = 'l25']



// input [@ value = '4']



// a [@ href = 'www.walmart. com ']



ضع في اعتبارك أمثلة أكثر تعقيدًا لـ html'i المحدد:



<div class ='contentBlock'>
  <div class = 'listItem'>
    <a class = 'link' href = 'habr.com'>
       <span class='name'>habr</span>
    </a>
    <div class = 'textConainer'>
      <span class='description'>cool site</span>
      "text2"
    </div>
  </div> 
  <div class = 'listItem'>
    <a class = 'link' href = 'habr.com'>
       <span class='name'>habrhabr</span>
    </a>
    <div class = 'textConainer'>
      <span class='description'>the same site</span>
      "text1"
    </div>
  </div> 
</div>


XPath = // div [@ class = 'contentBlock'] // div



سيتم تحديد العناصر التالية لـ XPath هذا:



<div class = 'listItem'>
<div class = 'textConainer'>
<div class = 'listItem'>
<div class = 'textConainer'>


XPath = // div [@ class = 'contentBlock'] / div



<div class = 'listItem'>
<div class = 'listItem'>


لاحظ الفرق بين / (الجلب من عقدة الجذر) و // (يجلب العقد من العقدة الحالية بغض النظر عن موقعها). إذا لم يكن الأمر واضحًا ، فراجع الأمثلة أعلاه مرة أخرى.



// div [@ class = 'contentBlock'] / div [@ class = 'listItem'] / a [@ class = 'link'] / span [@ class = 'name']



هذا الطلب هو نفسه مع html هذا :



// div / div / a / span



// span [@ class = 'name']



// a [@ class = 'link'] / span [@ class = 'name']



// a [@ class = ' link 'وhref= 'habr.com'] / span



// span [text () = 'habr' or text () = 'habrhabr']



// div [@ class = 'listItem'] // span [@ class = 'name' ]



// أ [يحتوي على (href، 'habr')] / span



// span [يحتوي على (text ()، 'habr')]



النتيجة:



<span class='name'>habr</span>
<span class='name'>habrhabr</span>


// span [text () = 'habr'] / parent :: a / parent :: div



يساوي



// div / div [@ class = 'listItem'] [1]



النتيجة:



<div class = 'listItem'>


الأصل :: - إرجاع المستوى الأعلى للأصل.



هناك أيضًا ميزة رائعة جدًا مثل التالي - sibling :: - تُرجع العديد من العناصر في نفس المستوى بعد العنصر الحالي ، على غرار ما قبل الشقيق :: - تُرجع العديد من العناصر في نفس المستوى قبل المستوى الحالي.



// span [@ class = 'name'] / following-sibiling :: text () [1]



النتيجة:



"text1"
"text2"


أعتقد أنه أوضح الآن. لتوحيد المواد ، أنصحك بالذهاب إلى هذا الموقع وكتابة بعض الطلبات للعثور على بعض عناصر html'i.



<div class="item">
 <a class="link" data-uid="A8" href="https://www.walmart.com/grocery/?veh=wmt" title="Pickup & delivery">
  <span class="g_b">Pickup and delivery</span>
 </a>
 <a class="link" data-uid="A9" href="https://www.walmart.com/" title="Walmart.com">
  <span class="g_b">Walmart.com</span>
 </a>
</div>
<div class="item">
<a class="link" data-uid="B8" href="https://www.walmart.com/grocery/?veh=wmt" title="Savings spotlight">
  <span class="g_b">Savings spotlight</span>
 </a>
 <a class="link" data-uid="B9" href="https://www.walmartethics.com/content/walmartethics/it_it.html" title="Walmart.com">
  <span class="g_b">Walmart.com(Italian)</span>
  "italian virsion"
 </a>
</div>


الآن بعد أن عرفنا ما هو XPath ، دعنا نعود إلى كتابة الكود. نظرًا لأن الوسطاء في Habr لا يحبون المراهنات ، فإنهم سيحللون أسعار القهوة في وول مارت



string pathToFile = AppDomain.CurrentDomain.BaseDirectory + '\\';
IWebDriver driver = new ChromeDriver(pathToFile);

driver.Navigate().GoToUrl("https://walmart.com");
Thread.Sleep(5000);

IWebElement element = driver.FindElement(By.XPath("//button[@id='header-Header sparkButton']"));

element.Click();
Thread.Sleep(2000);

element = driver.FindElement(By.XPath("//button[@data-tl-id='GlobalHeaderDepartmentsMenu-deptButtonFlyout-10']"));

element.Click();
Thread.Sleep(2000);

element = driver.FindElement(By.XPath("//div[text()='Coffee']/parent::a"));
driver.Navigate().GoToUrl(element.GetAttribute("href"));
Thread.Sleep(10000);

List<string> names = new List<string>(), prices = new List<string>();

List<IWebElement> listOfElements =driver.FindElements(By.XPath("//div[@class='tile-content']/div[@class='tile-title']/div")).ToList();

foreach (IWebElement a in listOfElements)
     names.Add(a.Text);

listOfElements = driver.FindElements(By.XPath("//div[@class='tile-content']/div[@class='tile-price']/span/span[contains(text(),'$')]")).ToList();

foreach (IWebElement a in listOfElements)
         prices.Add(a.Text);

for (int i = 0; i < prices.Count; i++)
      Console.WriteLine(names[i] + " " + prices[i]);


تم كتابة الموضوع للنوم بحيث يكون لصفحة الويب وقت للتحميل.



سيقوم البرنامج بفتح موقع متجر Walmart ، والضغط على اثنين من الأزرار ، وفتح قسم القهوة والحصول على اسم وأسعار البضائع.



إذا كانت صفحة الويب كبيرة جدًا وبالتالي تستغرق XPath وقتًا طويلاً أو يصعب كتابتها ، فأنت بحاجة إلى استخدام طريقة أخرى.



طلبات HTTP



أولاً ، دعونا نلقي نظرة على كيفية ظهور المحتوى على الموقع.







بكلمات بسيطة ، يقوم المتصفح بتقديم طلب إلى الخادم مع طلب توفير المعلومات اللازمة ، ويقدم الخادم بدوره هذه المعلومات. كل هذا يتم باستخدام طلبات HTTP.



للنظر في الطلبات التي يرسلها متصفحك إلى موقع معين ، ما عليك سوى فتح هذا الموقع ، والضغط على F12 والانتقال إلى علامة التبويب "الشبكة" ، ثم إعادة تحميل الصفحة.







الآن يبقى أن نجد الطلب الذي نحتاجه.



كيف افعلها؟ - ضع في اعتبارك جميع الطلبات من نوع الجلب (العمود الثالث في الصورة أعلاه) وانظر إلى علامة التبويب معاينة.







إذا لم يكن فارغًا ، فيجب أن يكون بتنسيق XML أو JSON ، وإذا لم يكن كذلك ، فاستمر في البحث. إذا كان الأمر كذلك ، تحقق مما إذا كانت المعلومات التي تحتاجها موجودة هنا. للتحقق من ذلك ، أنصحك باستخدام نوع من عارض JSON أو عارض XML (google وافتح الرابط الأول ، انسخ النص من علامة التبويب الاستجابة والصقه في العارض). عندما تجد الطلب الذي تحتاجه ، احفظ اسمه (العمود الأيسر) أو مضيف عنوان URL (علامة تبويب العناوين) في مكان ما ، بحيث لا تبحث لاحقًا. على سبيل المثال ، إذا فتحت قسمًا للقهوة على موقع Walmart ، فسيتم إرسال طلب ، ويبدأ القانون الخاص به بـ walmart.com/cp/api/wpa. سيكون هناك كل المعلومات حول القهوة للبيع.







مر منتصف الطريق ، والآن يمكن "تزوير" هذا الطلب وإرساله على الفور من خلال البرنامج ، وتلقي المعلومات اللازمة في غضون ثوانٍ. يبقى تحليل JSON أو XML ، وهذا أسهل بكثير من كتابة XPaths. ولكن غالبًا ما يكون تكوين مثل هذه الطلبات أمرًا غير سار إلى حد ما (انظر عنوان URL في الصورة أعلاه) وإذا نجحت ، فستتلقى في بعض الحالات مثل هذه الاستجابة.



{
  "detail": "No authorization token provided",
  "status": 401,
  "title": "Unauthorized",
  "type": "about:blank"
}


سوف تتعلم الآن كيف يمكنك تجنب مشاكل تقليد طلب باستخدام بديل - خادم وكيل.



مخدم بروكسي



الخادم الوكيل هو جهاز يتوسط بين الكمبيوتر والإنترنت.







سيكون من الرائع أن يكون برنامجنا خادمًا وكيلاً ، ثم يمكنك معالجة الاستجابات الضرورية من الخادم بسرعة وسهولة. ثم سيكون هناك مثل هذا المتصفح المتسلسل - البرنامج - الإنترنت (خادم الموقع الذي يتم تحليله).



لحسن الحظ ، توجد مكتبة رائعة لمثل هذه الاحتياجات - Titanium Web Proxy.



لنقم بإنشاء فئة PServer



class PServer
    {
        private static ProxyServer proxyServer;

        public PServer()
        {
            proxyServer = new ProxyServer();
            proxyServer.BeforeResponse += OnResponse;
            var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Loopback, 8000, true);
            explicitEndPoint.BeforeTunnelConnectRequest += OnBeforeTunnelConnectRequest;
            proxyServer.AddEndPoint(explicitEndPoint);
            proxyServer.Start();

        }
        private async Task OnBeforeTunnelConnectRequest(object sender, TunnelConnectSessionEventArgs e)
        {
            if (!e.HttpClient.Request.Url.Contains("www.walmart.com")){ 
                 e.DecryptSsl = false;
            }
        }
        public async Task OnResponse(object sender, SessionEventArgs e)
        {
            if (e.HttpClient.Response.StatusCode == 200 && (e.HttpClient.Request.Method == "GET" || e.HttpClient.Request.Method == "POST"))
            {
                string url = e.HttpClient.Request.Url;
                if (url.Contains("walmart.com/cp/api/wpa")){
                    Console.WriteLine(await e.GetResponseBodyAsString());
                }
            }
        }
    }


الآن دعنا ننتقل إلى كل طريقة على حدة.



public PServer()
      {
          proxyServer = new ProxyServer();
          proxyServer.BeforeResponse += OnResponse;
          var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Loopback, 8000, true);
          explicitEndPoint.BeforeTunnelConnectRequest += OnBeforeTunnelConnectRequest;
          proxyServer.AddEndPoint(explicitEndPoint);
          proxyServer.Start();
      }


proxyServer.BeforeRepsone + = OnRespone - أضف طريقة لمعالجة استجابة من الخادم. سيتم استدعاؤه تلقائيًا عند وصول الرد.



صريح



إندبوينت - تكوين الخادم الوكيل ، ExplicitProxyEndPoint (IPAddress ipAddress ، منفذ int ، bool decryptSsl = true) عنوان IP

والمنفذ الذي يعمل عليه الخادم الوكيل.



decryptSsl - سواء لفك تشفير SSL. بمعنى آخر ، إذا كانت decrtyptSsl = true ، فسيعالج الخادم الوكيل جميع الطلبات والاستجابات.



clearEndPoint.BeforeTunnelConnectRequest + = OnBeforeTunnelConnectRequest - أضف طريقة لمعالجة الطلب قبل إرساله إلى الخادم. كما سيتم استدعاؤه تلقائيًا قبل إرسال الطلب.



proxyServer.Start () - "بدء" الخادم الوكيل ، من هذه اللحظة يبدأ في معالجة الطلبات والاستجابات.



private async Task OnBeforeTunnelConnectRequest(object sender, TunnelConnectSessionEventArgs e)
        {
            if (!e.HttpClient.Request.Url.Contains("www.walmart.com")){ 
                 e.DecryptSsl = false;
            }
        }


e.DecryptSsl = false - لن تتم معالجة الطلب والاستجابة الحاليين.



إذا لم نكن مهتمين بالطلب أو الرد عليه (على سبيل المثال ، صورة أو نوع من النص) ، فلماذا فك تشفيره؟ يتم إنفاق الكثير من الموارد على هذا ، وإذا تم فك تشفير جميع الطلبات والاستجابات ، فسيعمل البرنامج لفترة طويلة. لذلك ، إذا كان الطلب الحالي لا يحتوي على مضيف الطلب الذي نهتم به ، فلا فائدة من فك تشفيره.



public async Task OnResponse(object sender, SessionEventArgs e)
        {
            if (e.HttpClient.Response.StatusCode == 200 && (e.HttpClient.Request.Method == "GET" || e.HttpClient.Request.Method == "POST"))
            {
                string url = e.HttpClient.Request.Url;
                if (url.Contains("walmart.com/cp/api/wpa")) Console.WriteLine(await e.GetResponseBodyAsString());
            }
        }
    }


انتظار e.GetResponseBodyAsString () - إرجاع استجابة كسلسلة.



لكي يتصل WebDriver بالخادم الوكيل ، تحتاج إلى كتابة ما يلي:



string pathToFile = AppDomain.CurrentDomain.BaseDirectory + '\\';
ChromeOptions options = new ChromeOptions();
options.AddArguments("--proxy-server=" + IPAddress.Loopback + ":8000");
IWebDriver driver = new ChromeDriver(pathToFile, options);


الآن يمكنك التعامل مع الطلبات التي تريدها.



خاتمة



باستخدام WebDriver ، يمكنك التنقل بين الصفحات والنقر فوق الأزرار وتقليد سلوك المستخدم العادي. باستخدام XPaths ، يمكنك استخراج المعلومات التي تحتاجها من صفحات الويب. إذا لم تعمل XPaths ، فيمكن للخادم الوكيل المساعدة دائمًا ، والذي يمكنه اعتراض الطلبات بين المتصفح والموقع.



All Articles