1

Я делаю такой проект, в котором вводишь сколько долларов, и потом программа говорит тебе доллары в рублях. Я использую WebClient. Я написал что WebClient выполняет метод DownloadDataAsync, а потом написал событие DownloadDataCompleted. После, я создал метод в котором всё это обрабатывается. Я добавил Regex. С помощью символа .*? я попробовал найти из самой страницы cbr.ru всё что нужно. Я выбрал вот эту часть страницы:

<tr>
   <td class="">840</td>
   <td class="">USD</td>
   <td class="">1</td>
   <td class="">Доллар США</td>
   <td class="">77,2759</td>
</tr>

Когда я начал выполнять код, то, при выводе он не может найти группу( (.*?) ) Вот сам код:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace PARSER { public partial class Form1 : Form { public Form1() { InitializeComponent(); }

    private void DataCallback(Object sender, DownloadDataCompletedEventArgs e)
    {
        byte[] data = (byte[])e.Result;
        string textData = System.Text.Encoding.UTF8.GetString(data);
        Match match = Regex.Match(textData, &quot;&lt;tr&gt;&lt;td class=\&quot;\&quot;&gt;840&lt;/td&gt;&lt;td class=\&quot;\&quot;&gt;USD&lt;/td&gt;&lt;td class=\&quot;\&quot;&gt;1&lt;/td&gt;&lt;td class=\&quot;\&quot;&gt;Доллар США&lt;/td&gt;&lt;td class=\&quot;\&quot;&gt;(.*?)&lt;/td&gt;&lt;/tr&gt;&quot;, RegexOptions.Singleline);
        richTextBox1.Text = &quot;lol&quot; + match.Groups[1].ToString();
    }
    async private void button1_Click(object sender, EventArgs e)
    {
        double dollars = Convert.ToDouble(textBox1.Text);

        using (WebClient webClient = new WebClient())
        {
            webClient.DownloadDataCompleted += new DownloadDataCompletedEventHandler(DataCallback);
            webClient.DownloadDataAsync(new Uri(&quot;https://www.cbr.ru/currency_base/daily/&quot;));
        }
    }
}

}

P.s.: я хочу сначала получить цену доллара, потом уже из textBox1 получить правильное произведение.

A K
  • 28,718

4 Answers4

1

Не самое хорошее решение использовать регулярки для парсинга HTML страниц. При любом изменении контента страницы ваша регулярка перестанет работать. Посмотрите в сторону - html-agility-pack

Если говорить о вашем решение то вы не учли несколько моментов:

  1. Экранировать символ '/'
  2. Наличие спецсимволов между тегами новых строк

Если исправлять ваше решение, то регулярное выражение может выглядеть так:

<tr>\W*<td class=\"\">840<\/td>\W*<td class=\"\">USD<\/td>\W*<td class=\"\">1<\/td>\W*<td class=\"\">Доллар США<\/td>\W*<td class=\"\">(.*?)\W*<\/td>\W*<\/tr>

UPD: Что бы не было разночтений приведу код на C#

        var html = @"
        <tr>
        <td class="""">840</td>
        <td class="""">USD</td>
        <td class="""">1</td>
        <td class="""">Доллар США</td>
        <td class="""">77,2759</td>
        </tr>";
    var result = Regex.Match(html, &quot;&lt;tr&gt;\\W*&lt;td class=\&quot;\&quot;&gt;840&lt;\\/td&gt;\\W*&lt;td class=\&quot;\&quot;&gt;USD&lt;\\/td&gt;\\W*&lt;td class=\&quot;\&quot;&gt;1&lt;\\/td&gt;\\W*&lt;td class=\&quot;\&quot;&gt;Доллар США&lt;\\/td&gt;\\W*&lt;td class=\&quot;\&quot;&gt;(.*?)\\W*&lt;\\/td&gt;\\W*&lt;\\/tr&gt;&quot;, RegexOptions.Singleline);

    if (result.Success)
    {
        Console.WriteLine($&quot;full match: {result.Groups[0].Value}&quot;);
        Console.WriteLine($&quot;dollar rate: {result.Groups[1].Value}&quot;);
    }

kurtov
  • 391
  • В вашей строке надо либо экранировать все '', либо убрать все экранирования и поставить '@' перед строкой – timur Oct 15 '20 at 06:43
  • При вводе вашей строки, появляется много. ошибок. – NightBPhoenix Oct 15 '20 at 07:12
  • Добавил пример на C# – kurtov Oct 15 '20 at 07:18
  • @NightBPhoenix Так как у вас все грубо завязано на контент, то нет смысла в регулярке указывать все данные из . Достаточно будет грубой и короткой записи: Regex.Match(textData, @"Доллар США</td>\s*<td.*>(.*?)</td>") – kurtov Oct 15 '20 at 07:39
1

Использовать регулярки для парсинга - не самая лучшая идея. (You can't parse [X]HTML with regex).

Но если вы хотите, то используйте @ перед строкой, учитывайте пробельные символы и уберите class="", потому что там нет такого:

Regex.Match(textData, 
       @"<tr>\s*<td>840</td>\s*<td>USD</td>\s*<td>1</td>\s*<td>Доллар США</td>\s*<td>(.*?)</td>\s*</tr>"
timur
  • 3,570
  • 2
  • 9
  • 32
1

Не стоит под такие задачи использовать регулярки (см. Проблема XY), стоит использовать парсеры html такие как AngleSharp или HtmlAgilityPack.

Ваш пример на AngleSharp:

var source = @"<html><body><table><tr>
       <td class="""">840</td>
       <td class=""myclass"">USD</td>
       <td class="""">1</td>
       <td class="""">Доллар США</td>
       <td class="""">77,2759</td>
    </tr></table></body></html>";

var parser = new HtmlParser(); var document = parser.ParseDocument(source);

var currency = document.QuerySelector("td.myclass"); Console.WriteLine(currency.TextContent);

A K
  • 28,718
  • использовать двойные ковычки внутри двойных ковычек не самая хорошая идея. Из-за этого использования в больших программах может произойти беда. Случилось однажды, пока делал мини-проект с 240 строками – NightBPhoenix Oct 16 '20 at 03:59
  • @NightBPhoenix Это фрагмент скрипта в linqpad, он умеет по правому клику вставлять строку автоматически добавив кавычки. Разумеется в реальных проектах такого не будет. – A K Oct 16 '20 at 07:40
1
  • HTML - это не регулярная структура, поэтому парсить HTML регулярками - очень медленно и сложно. Лучше использовать готовый парсер, например HtmlAgilityPack.
  • WebClient устарел, лучше использовать HttpClient вместо него.

При том у вас большая регулярка получает только одну валюту, но можно загрузить все.

Для хранения данных валюты, полученных из таблицы, я создал класс

public class Currency
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Quantity { get; set; }
    public string Text { get; set; }
    public double Value { get; set; }
}

Далее, я установил 2 NuGet пакета

  • HtmlAgilityPack - сам парсер
  • Fizzler.Systems.HtmlAgilityPack - его расширение QuerySelector

QuerySelector принимает на вход тот же синтаксис запроса, который используется в querySelector в JavaScript.

Получился вот такой код:

using HtmlAgilityPack;
using Fizzler.Systems.HtmlAgilityPack;
public partial class Form1 : Form
{
    private static HttpClient client = new HttpClient();
public Form1()
{
    InitializeComponent();
}

private async void button1_Click(object sender, EventArgs e)
{
    try
    {
        double amount = double.Parse(textBox1.Text);
        List&lt;Currency&gt; currencies = await GetCurrenciesAsync();
        Currency usd = currencies.FirstOrDefault(x =&gt; x.Name == &quot;USD&quot;);
        double usdValue = usd.Value / usd.Quantity;
        richTextBox1.Text = (usdValue * amount).ToString();
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

private async Task&lt;List&lt;Currency&gt;&gt; GetCurrenciesAsync()
{
    string html = await client.GetStringAsync(&quot;https://www.cbr.ru/currency_base/daily/&quot;);
    HtmlAgilityPack.HtmlDocument doc = new HtmlAgilityPack.HtmlDocument();
    doc.LoadHtml(html);
    IEnumerable&lt;HtmlNode&gt; nodes = doc.DocumentNode.QuerySelectorAll(&quot;.data tbody tr&quot;);
    List&lt;Currency&gt; currencies = new List&lt;Currency&gt;();
    foreach (HtmlNode node in nodes)
    {
        string[] cells = node.QuerySelectorAll(&quot;td&quot;).Select(x =&gt; x.InnerText).ToArray();
        if (cells.Length == 5)
        {
            currencies.Add(new Currency
            {
                Id = int.Parse(cells[0]),
                Name = cells[1],
                Quantity = int.Parse(cells[2]),
                Text = cells[3],
                Value = double.Parse(cells[4])
            });
        }
    }
    return currencies;
}

}

Ввожу в textBox1 число 2, получаю вывод в richTextBox1 - 154,5518. При этом все валюты загружены, можно курс любой валюты получить и использовать точно таким же образом.


Но с ЦБР можно обойтись без парсинга HTML - у них есть API. Можно создать классы для хранения данных, полученных из JSON и распарсить одной строчкой кода с помощью NuGet пакета Newtonsoft.Json или, если у вас .NET Core 3.1 или новее, то с помощью встроенного в фреймворк JSON парсера System.Text.Json.

aepot
  • 49,560