Почему Regex не находит в строке?
Я делаю такой проект, в котором вводишь сколько долларов, и потом программа говорит тебе доллары в рублях. Я использую 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, "<tr><td class=\"\">840</td><td class=\"\">USD</td><td class=\"\">1</td><td class=\"\">Доллар США</td><td class=\"\">(.*?)</td></tr>", RegexOptions.Singleline);
richTextBox1.Text = "lol" + 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("https://www.cbr.ru/currency_base/daily/"));
}
}
}
}
P.s.: я хочу сначала получить цену доллара, потом уже из textBox1 получить правильное произведение.
Ответы (4 шт):
Не самое хорошее решение использовать регулярки для парсинга HTML страниц. При любом изменении контента страницы ваша регулярка перестанет работать. Посмотрите в сторону - html-agility-pack
Если говорить о вашем решение то вы не учли несколько моментов:
- Экранировать символ '/'
- Наличие спецсимволов между тегами новых строк
Если исправлять ваше решение, то регулярное выражение может выглядеть так:
<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, "<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>", RegexOptions.Singleline);
if (result.Success)
{
Console.WriteLine($"full match: {result.Groups[0].Value}");
Console.WriteLine($"dollar rate: {result.Groups[1].Value}");
}
Использовать регулярки для парсинга - не самая лучшая идея. (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>"
Не стоит под такие задачи использовать регулярки (см. Проблема 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);
- 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<Currency> currencies = await GetCurrenciesAsync();
Currency usd = currencies.FirstOrDefault(x => x.Name == "USD");
double usdValue = usd.Value / usd.Quantity;
richTextBox1.Text = (usdValue * amount).ToString();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private async Task<List<Currency>> GetCurrenciesAsync()
{
string html = await client.GetStringAsync("https://www.cbr.ru/currency_base/daily/");
HtmlAgilityPack.HtmlDocument doc = new HtmlAgilityPack.HtmlDocument();
doc.LoadHtml(html);
IEnumerable<HtmlNode> nodes = doc.DocumentNode.QuerySelectorAll(".data tbody tr");
List<Currency> currencies = new List<Currency>();
foreach (HtmlNode node in nodes)
{
string[] cells = node.QuerySelectorAll("td").Select(x => 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.