2013-08-24 2 views
2

Я разрабатываю веб-скребок, но мне нужно сохранять куки между запросами, как я могу сделать на PHP, используя curl. Однако кажется, что если я попытаюсь использовать объект CookieContainer в C#, он не будет захватывать все файлы cookie из ответа и отправить их на следующий запрос.Внедрение веб-скребка в C#

Вот мой C# класс:

public class Scraper 
    { 
     public string Username { get; set; } 
     public string Password { get; set; } 
     public string UserAgent { get; set; } 
     public string ContentType { get; set; } 
     public CookieCollection Cookies { get; set; } 
     public CookieContainer Container { get; set; } 

     public Scraper() 
     { 
      UserAgent = "Mozilla/5.0 (Windows NT 6.2; WOW64; rv:18.0) Gecko/20100101 Firefox/18.0"; 
      ContentType = "application/x-www-form-urlencoded"; 
      Cookies = new CookieCollection(); 
      Container = new CookieContainer(); 
     } 

     public string Load(string uri, string postData = "", NetworkCredential creds = null, int timeout = 60000, string host = "", string referer = "", string requestedwith = "") 
     { 
      HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri); 
      request.CookieContainer = Container; 
      request.CookieContainer.Add(Cookies); 
      request.UserAgent = UserAgent; 
      request.AllowWriteStreamBuffering = true; 
      request.ProtocolVersion = HttpVersion.Version11; 
      request.AllowAutoRedirect = true; 
      request.ContentType = ContentType; 
      request.PreAuthenticate = true; 

      if (requestedwith.Length > 0) 
       request.Headers["X-Requested-With"] = requestedwith; 

      if (host.Length > 0) 
       request.Host = host; 

      if (referer.Length > 0) 
       request.Referer = referer; 

      if (timeout > 0) 
       request.Timeout = timeout; 

      if (creds != null) 
       request.Credentials = creds; 

      if (postData.Length > 0) 
      { 
       request.Method = "POST"; 
       ASCIIEncoding encoding = new ASCIIEncoding(); 
       byte[] data = encoding.GetBytes(postData); 
       request.ContentLength = data.Length; 
       Stream newStream = request.GetRequestStream(); //open connection 
       newStream.Write(data, 0, data.Length); // Send the data. 
       newStream.Close(); 
      } 
      else 
       request.Method = "GET"; 

      HttpWebResponse response = (HttpWebResponse)request.GetResponse(); 
      Cookies = response.Cookies; 
      StringBuilder page; 
      using (StreamReader sr = new StreamReader(response.GetResponseStream())) 
      { 
       page = new StringBuilder(sr.ReadToEnd()); 
       page = page.Replace("\r\n", ""); // strip all new lines and tabs 
       page = page.Replace("\r", ""); // strip all new lines and tabs 
       page = page.Replace("\n", ""); // strip all new lines and tabs 
       page = page.Replace("\t", ""); // strip all new lines and tabs 
      } 

      string str = page.ToString(); 
      str = Regex.Replace(str, @">\s+<", "><"); 

      return str; 
     } 
    } 

Вот мой PHP код для загрузки и сохранения печенье в куков:

private function load($url = 'http://www.google.com/', $postData = array(), $headers = FALSE) 
    { 
     $useragent = "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; " . $this->locale . "; rv:1.9.2.10) Gecko/20100914 BRI/1 Firefox/3.6.10 (.NET CLR 3.5.30729)"; 

     $curl = curl_init(); 
     curl_setopt($curl, CURLOPT_URL, $url); 
     curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE); 
     curl_setopt($curl, CURLOPT_HEADER, FALSE); 
     if($headers) curl_setopt($curl, CURLOPT_HTTPHEADER, array('X-Requested-With: XMLHttpRequest')); 
     curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE); 
     curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE); 
     curl_setopt($curl, CURLOPT_ENCODING, 'UTF-8'); 
     curl_setopt($curl, CURLOPT_USERAGENT, $useragent); 
     curl_setopt($curl, CURLOPT_POST, !empty($postData)); 
     if(!empty($postData)) curl_setopt($curl, CURLOPT_POSTFIELDS, $postData); 
     curl_setopt($curl, CURLOPT_COOKIEFILE, $this->cookieFile); 
     curl_setopt($curl, CURLOPT_COOKIEJAR, $this->cookieFile); 
     $page = curl_exec ($curl); 
     $page = str_replace(array("\r\n", "\r", "\n", "\t"), "", $page); // strip all new lines and tabs 
     $page = preg_replace('~>\s+<~', '><', $page);// strip all whitespace between tags 
     curl_close ($curl); 

     return $page; 
    } 

Как успешно поддерживать куки между запросами?

+0

Я посмотрел на свой код и не мог видеть что-то концептуально неправильно с ним. Я запустил его в тестовом приложении, чтобы убедиться, что он не работает, но он работает так, как ожидалось. Конечно, первый запрос не имеет данных cookie, все последующие запросы (с использованием того же экземпляра Scraper) содержат информацию cookie, подтвержденную с помощью Fiddler. Это может не сработать, если вы загружаете несколько доменов, поскольку файлы cookie обычно являются специфичными для домена. Как только вы дважды запрашиваете тот же домен, он работает хорошо. Можете ли вы подробнее рассказать о том, чего вы ожидаете? – BrutalDev

+0

Извините, что так долго, чтобы вернуться к вам. Я пытаюсь войти на xbox.com, и у меня есть рабочая реализация на PHP, но я хочу переместить мой код на C#. Однако, похоже, он не хранит файлы cookie правильно между запросами в C#. В PHP есть файл cookie, который поддерживает всю информацию cookie между запросами. Мне нужна аналогичная реализация в C#, но, насколько я знаю, C# работает только в хранилище файлов cookie и не сохраняет их на диске. –

+1

Хорошо, что имеет смысл. Вы можете просто сохранить контейнер cookie на диск и прочитать его обратно из кода скребка, прежде чем он начнет любую обработку. Поскольку CookieContainer является сериализуемым, вы можете читать и писать это довольно легко в различных форматах: http://stackoverflow.com/questions/1777203/c-writing-a-cookiecontainer-to-disk-and-loading-back-in- for-use – BrutalDev

ответ

2

Я нашел .NET-оболочку для libcurl под названием LibCurl.NET и смог обрабатывать файлы cookie так же, как cURL с PHP с C#! Вот мой код для всех, кто интересуется:

using SeasideResearch.LibCurlNet; 
using System; 
using System.Collections.Generic; 
using System.Drawing; 
using System.IO; 
using System.Linq; 
using System.Net; 
using System.Text; 
using System.Text.RegularExpressions; 

namespace Scraping 
{ 
    public class LibCurlScraper 
    { 
     StringBuilder sb = new StringBuilder(); 
     MemoryStream ms = new MemoryStream(); 
     public string CookieFile { get; set; } 
     public string RedirectUrl { get; set; } 
     public string UserAgent { get; set; } 
     public string ContentType { get; set; } 
     public bool DisplayHeaders { get; set; } 
     public bool FollowRedirects { get; set; } 

     public LibCurlScraper() 
     { 
      UserAgent = "useragent"; 
      ContentType = "application/x-www-form-urlencoded"; 
      Curl.GlobalInit((int)CURLinitFlag.CURL_GLOBAL_ALL); 
      DisplayHeaders = false; 
     } 

     private int MyWriteFunction(byte[] buf, int size, int nmemb, Object extraData) 
     { 
      foreach (byte b in buf) 
      { 
       //Console.Write((char)b); 
       sb.Append((char)b); 
      } 

      return buf.Length; 
     } 

     private int MyWriteBinaryFunction(byte[] buf, int size, int nmemb, Object extraData) 
     { 
      foreach (byte b in buf) 
      { 
       //Console.Write((char)b); 
       ms.WriteByte(b); 
      } 

      return buf.Length; 
     } 

     public MemoryStream LoadBinary(string uri, string method = "GET", string postData = "", List<string> headers = null) 
     { 
      ms = new MemoryStream(); 
      Easy easy = new Easy(); 
      Easy.WriteFunction wf = MyWriteBinaryFunction; 
      easy.SetOpt(CURLoption.CURLOPT_URL, uri); 
      easy.SetOpt(CURLoption.CURLOPT_HEADER, false); 
      easy.SetOpt(CURLoption.CURLOPT_FOLLOWLOCATION, true); 

      Slist headerSlist = new Slist(); 

      if (headers != null) 
      { 
       foreach (var header in headers) 
       { 
        headerSlist.Append(header); 
       } 

      } 

      easy.SetOpt(CURLoption.CURLOPT_HTTPHEADER, headerSlist); 

      easy.SetOpt(CURLoption.CURLOPT_SSL_VERIFYPEER, false); 
      easy.SetOpt(CURLoption.CURLOPT_SSL_VERIFYHOST, false); 
      easy.SetOpt(CURLoption.CURLOPT_USERAGENT, UserAgent); 
      easy.SetOpt(CURLoption.CURLOPT_TIMEOUT, 10); 
      easy.SetOpt(CURLoption.CURLOPT_CONNECTTIMEOUT, 3); 

      if (!string.IsNullOrEmpty(postData)) 
      { 
       easy.SetOpt(CURLoption.CURLOPT_POST, true); 
       easy.SetOpt(CURLoption.CURLOPT_POSTFIELDS, postData); 
      } 

      easy.SetOpt(CURLoption.CURLOPT_COOKIEFILE, CookieFile); 
      easy.SetOpt(CURLoption.CURLOPT_COOKIEJAR, CookieFile); 
      easy.SetOpt(CURLoption.CURLOPT_WRITEFUNCTION, wf); 
      easy.Perform(); 
      int code = 0; 
      easy.GetInfo(CURLINFO.CURLINFO_RESPONSE_CODE, ref code); 
      easy.Cleanup(); 

      return ms; 
     } 

     public string Load(string uri, string method = "GET", string postData = "", List<string> headers = null) 
     { 
      sb.Clear(); 
      Easy easy = new Easy(); 
      Easy.WriteFunction wf = MyWriteFunction; 
      easy.SetOpt(CURLoption.CURLOPT_URL, uri); 
      easy.SetOpt(CURLoption.CURLOPT_HEADER, DisplayHeaders); 
      easy.SetOpt(CURLoption.CURLOPT_FOLLOWLOCATION, FollowRedirects); 

      Slist headerSlist = new Slist(); 

      if (headers != null) 
      { 
       foreach (var header in headers) 
       { 
        headerSlist.Append(header); 
       } 

      } 

      easy.SetOpt(CURLoption.CURLOPT_HTTPHEADER, headerSlist); 


      easy.SetOpt(CURLoption.CURLOPT_SSL_VERIFYPEER, false); 
      easy.SetOpt(CURLoption.CURLOPT_SSL_VERIFYHOST, false); 
      easy.SetOpt(CURLoption.CURLOPT_USERAGENT, UserAgent); 
      easy.SetOpt(CURLoption.CURLOPT_TIMEOUT, 10); 
      easy.SetOpt(CURLoption.CURLOPT_CONNECTTIMEOUT, 3); 

      if (!string.IsNullOrEmpty(postData)) 
      { 
       easy.SetOpt(CURLoption.CURLOPT_POST, true); 
       easy.SetOpt(CURLoption.CURLOPT_POSTFIELDS, postData); 
      } 

      if (method.Equals("POST")) 
      { 
       easy.SetOpt(CURLoption.CURLOPT_POST, true); 
      } 

      easy.SetOpt(CURLoption.CURLOPT_COOKIEFILE, CookieFile); 
      easy.SetOpt(CURLoption.CURLOPT_COOKIEJAR, CookieFile); 
      easy.SetOpt(CURLoption.CURLOPT_WRITEFUNCTION, wf); 
      easy.Perform(); 
      int code = 0; 
      easy.GetInfo(CURLINFO.CURLINFO_RESPONSE_CODE, ref code); 
      easy.Cleanup(); 

      //Console.WriteLine(code); 
      if (code == 302) 
      { 
       RedirectUrl = FindString(sb.ToString(), "Location:(.*?)\n"); 
       //Console.WriteLine(RedirectUrl); 
      } 


      string page = sb.ToString(); 
      page = page.Replace("\r\n", ""); // strip all new lines and tabs 
      page = page.Replace("\r", ""); // strip all new lines and tabs 
      page = page.Replace("\n", ""); // strip all new lines and tabs 
      page = page.Replace("\t", ""); // strip all new lines and tabs 

      page = Regex.Replace(page, @">\s+<", "><"); 

      return page; 
     } 

     public static void OnDebug(CURLINFOTYPE infoType, String msg, Object extraData) 
     { 
      Console.WriteLine(msg); 
      TextWriter tw = new StreamWriter(@"C:\cookies\verbose.txt", true); 
      tw.WriteLine(msg); 
      tw.Close(); 
     } 
    } 
} 

У меня есть два метода, один для возвращения строки и один для возвращения MemoryStream. Вам нужно будет инициализировать свойство CookieFile и убедиться, что каталог/файл можно записать перед попыткой записать в файл.

Я заметил, что проблемы возникают, если ваш файл cookie содержит старые данные сеанса из предыдущего прогона. Это можно устранить, удалив файл cookie перед созданием нового экземпляра LibCurlScraper и заполнением файла cookie.

В идеале мы могли бы использовать встроенные управляемые классы для всех HTTP-файлов cookie, но это работает до тех пор, пока не будет найдено лучшее решение.

EDIT:
я наткнулся на код, который правильно анализирует заголовок "Set-Cookie". Он обрабатывает файлы cookie, разделенные запятыми, и извлекает имя, истечение, путь, значение и домен каждого файла cookie. Это должен быть предпочтительный способ сделать HTTP-запросы, а не LibCurl.NET. Вы также можете применить этот метод к асинхронным запросам.

Этот код работает лучше, чем собственный синтаксический анализатор файлов cookie Microsoft, и это действительно то, что должно делать официальный парсивер cookie. Я не знаю, почему Microsoft еще не исправила это, поскольку это очень распространенная проблема.

Вот исходный код: http://snipplr.com/view/4427/

Я отправляю его здесь, в случае, если связь идет вниз в какой-то момент:

public static CookieCollection GetAllCookiesFromHeader(string strHeader, string strHost) 
{ 
    ArrayList al = new ArrayList(); 
    CookieCollection cc = new CookieCollection(); 
    if (strHeader != string.Empty) 
    { 
     al = ConvertCookieHeaderToArrayList(strHeader); 
     cc = ConvertCookieArraysToCookieCollection(al, strHost); 
    } 
    return cc; 
} 


private static ArrayList ConvertCookieHeaderToArrayList(string strCookHeader) 
{ 
    strCookHeader = strCookHeader.Replace("\r", ""); 
    strCookHeader = strCookHeader.Replace("\n", ""); 
    string[] strCookTemp = strCookHeader.Split(','); 
    ArrayList al = new ArrayList(); 
    int i = 0; 
    int n = strCookTemp.Length; 
    while (i < n) 
    { 
     if (strCookTemp[i].IndexOf("expires=", StringComparison.OrdinalIgnoreCase) > 0) 
     { 
      al.Add(strCookTemp[i] + "," + strCookTemp[i + 1]); 
      i = i + 1; 
     } 
     else 
     { 
      al.Add(strCookTemp[i]); 
     } 
     i = i + 1; 
    } 
    return al; 
} 


private static CookieCollection ConvertCookieArraysToCookieCollection(ArrayList al, string strHost) 
{ 
    CookieCollection cc = new CookieCollection(); 

    int alcount = al.Count; 
    string strEachCook; 
    string[] strEachCookParts; 
    for (int i = 0; i < alcount; i++) 
    { 
     strEachCook = al[i].ToString(); 
     strEachCookParts = strEachCook.Split(';'); 
     int intEachCookPartsCount = strEachCookParts.Length; 
     string strCNameAndCValue = string.Empty; 
     string strPNameAndPValue = string.Empty; 
     string strDNameAndDValue = string.Empty; 
     string[] NameValuePairTemp; 
     Cookie cookTemp = new Cookie(); 

     for (int j = 0; j < intEachCookPartsCount; j++) 
     { 
      if (j == 0) 
      { 
       strCNameAndCValue = strEachCookParts[j]; 
       if (strCNameAndCValue != string.Empty) 
       { 
        int firstEqual = strCNameAndCValue.IndexOf("="); 
        string firstName = strCNameAndCValue.Substring(0, firstEqual); 
        string allValue = strCNameAndCValue.Substring(firstEqual + 1, strCNameAndCValue.Length - (firstEqual + 1)); 
        cookTemp.Name = firstName; 
        cookTemp.Value = allValue; 
       } 
       continue; 
      } 
      if (strEachCookParts[j].IndexOf("path", StringComparison.OrdinalIgnoreCase) >= 0) 
      { 
       strPNameAndPValue = strEachCookParts[j]; 
       if (strPNameAndPValue != string.Empty) 
       { 
        NameValuePairTemp = strPNameAndPValue.Split('='); 
        if (NameValuePairTemp[1] != string.Empty) 
        { 
         cookTemp.Path = NameValuePairTemp[1]; 
        } 
        else 
        { 
         cookTemp.Path = "/"; 
        } 
       } 
       continue; 
      } 

      if (strEachCookParts[j].IndexOf("domain", StringComparison.OrdinalIgnoreCase) >= 0) 
      { 
       strPNameAndPValue = strEachCookParts[j]; 
       if (strPNameAndPValue != string.Empty) 
       { 
        NameValuePairTemp = strPNameAndPValue.Split('='); 

        if (NameValuePairTemp[1] != string.Empty) 
        { 
         cookTemp.Domain = NameValuePairTemp[1]; 
        } 
        else 
        { 
         cookTemp.Domain = strHost; 
        } 
       } 
       continue; 
      } 
     } 

     if (cookTemp.Path == string.Empty) 
     { 
      cookTemp.Path = "/"; 
     } 
     if (cookTemp.Domain == string.Empty) 
     { 
      cookTemp.Domain = strHost; 
     } 
     cc.Add(cookTemp); 
    } 
    return cc; 
} 
Смежные вопросы