Википедия:Заявки на статус бота/VortBot
вклад • правки • SUL • журналы • блокировать • лог блокировок • журнал изменения прав • присвоить флаг
Ответственный ботовод: Vort
Цель
[править код]Выполнение задач на ВП:РДБ. В данный момент собираюсь выполнить запрос ВП:РДБ#Bogus file options. Примеры правок: 1, 2, 3, 4. Планирую запускать обработку малыми партиями, оценивать отзывы и постепенно наращивать объёмы (50, 200, 500 правок).
Технические подробности
[править код]Выполнение указанного запроса РДБ происходит в 3 этапа: скачивание статей, синтаксический анализ викиразметки и замена фрагментов викитекста в автоматическом режиме. За каждый этап отвечает отдельная программа. Все они написаны на C#, используются непосредственно API вызовы MediaWiki. Так как за редактирование статей отвечает третья программа, то вот её исходный код:
using LinqToDB;
using LinqToDB.Mapping;
using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Web;
using System.Xml;
namespace WikiReplace
{
[Table(Name = "Articles")]
public class Article
{
[PrimaryKey]
public int PageId;
[Column()]
public int RevId;
[Column()]
public string Timestamp;
[Column()]
public string Title;
[Column()]
public string WikiText;
};
[Table(Name = "Replacements")]
public class Replacement
{
[PrimaryKey]
public int Id;
[Column()]
public int PageId;
[Column()]
public string SrcString;
[Column()]
public string DstString;
[Column()]
public byte Status;
}
public class Db : LinqToDB.Data.DataConnection
{
public Db() : base("Db") { }
public ITable<Article> Articles { get { return GetTable<Article>(); } }
public ITable<Replacement> Replacements { get { return GetTable<Replacement>(); } }
}
class Program
{
static Db db;
WebClient wc;
CookieContainer cookies;
string wikiUrl = "https://ru.wikipedia.org";
string ApiRequest(string getParameters, params string[] postParameters)
{
if (postParameters.Length % 2 != 0)
throw new Exception();
byte[] gzb = null;
for (;;)
{
string url = wikiUrl + "/w/api.php" + getParameters + "&maxlag=1";
if (postParameters != null)
{
var sb = new StringBuilder();
for (int i = 0; i < postParameters.Length / 2; i++)
{
if (i != 0)
sb.Append('&');
sb.Append(postParameters[i * 2]);
sb.Append('=');
sb.Append(HttpUtility.UrlEncode(postParameters[i * 2 + 1]));
}
gzb = wc.UploadData(url, Encoding.ASCII.GetBytes(sb.ToString()));
}
else
gzb = wc.DownloadData(url);
if (wc.ResponseHeaders["Retry-After"] != null)
{
int retrySec = int.Parse(wc.ResponseHeaders["Retry-After"]);
Thread.Sleep(retrySec * 1000);
}
else
break;
}
if (wc.ResponseHeaders["Set-Cookie"] != null)
{
var apiUri = new Uri(wikiUrl);
cookies.SetCookies(apiUri, wc.ResponseHeaders["Set-Cookie"]);
wc.Headers["Cookie"] = cookies.GetCookieHeader(apiUri);
}
GZipStream gzs = new GZipStream(
new MemoryStream(gzb), CompressionMode.Decompress);
MemoryStream xmls = new MemoryStream();
gzs.CopyTo(xmls);
byte[] xmlb = xmls.ToArray();
string xml = Encoding.UTF8.GetString(xmlb);
return xml;
}
string GetToken(string type)
{
string xml = ApiRequest(
"?action=query" +
"&format=xml" +
"&meta=tokens" +
"&type=" + type);
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
return doc.SelectNodes("/api/query/tokens")[0].Attributes[type + "token"].InnerText;
}
bool Login(string token, string name, string password)
{
string xml = ApiRequest(
"?action=login" +
"&format=xml",
"lgname", name,
"lgpassword", password,
"lgtoken", token);
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
return doc.SelectSingleNode("/api/login").Attributes["result"].InnerText == "Success";
}
bool EditPage(string csrfToken, string timestamp, int pageId, string summary, string text)
{
string xml = ApiRequest(
"?action=edit" +
"&format=xml",
"pageid", pageId.ToString(),
"summary", summary,
"text", text,
"basetimestamp", timestamp,
"token", csrfToken);
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
if (doc.SelectNodes("/api/error").Count == 1)
return false;
return doc.SelectSingleNode("/api/edit").Attributes["result"].InnerText == "Success";
}
Program()
{
string[] settings = File.ReadAllLines("auth.txt");
string botName = settings[0];
string botPassword = settings[1];
wc = new WebClient();
wc.Headers.Add("Accept-Encoding", "gzip");
wc.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
wc.Headers.Add("User-Agent", "WikiReplace ([[ru:u:Vort]])");
cookies = new CookieContainer();
string loginToken = GetToken("login");
if (!Login(loginToken, botName, botPassword))
{
Console.WriteLine("Login failed");
return;
}
string csrfToken = GetToken("csrf");
var articles = db.Articles.OrderByDescending(a => a.PageId).Take(40).ToArray();
foreach (var article in articles)
{
var replacements = db.Replacements.Where(
r => r.PageId == article.PageId && r.Status == 0).ToArray();
if (replacements.Length == 0)
continue;
string newText = article.WikiText;
foreach (var replacement in replacements)
newText = newText.Replace(replacement.SrcString, replacement.DstString);
bool isEditSuccessful = EditPage(csrfToken, article.Timestamp,
article.PageId, "исправление разметки ([[ВП:РДБ#Bogus file options]])", newText);
db.BeginTransaction();
foreach (var replacement in replacements)
{
replacement.Status = isEditSuccessful ? (byte)1 : (byte)2;
db.Update(replacement);
}
db.CommitTransaction();
Console.Write(isEditSuccessful ? '.' : 'x');
}
}
static void Main(string[] args)
{
db = new Db();
new Program();
db.Dispose();
}
}
}
Код писался быстро, поэтому выглядит не лучшим образом. Со временем планирую подправить архитектуру и стиль. Остальные два модуля тоже могу выложить, если потребуется.
Имеется флаг бота в других разделах Википедии?
[править код]Нет.
Обсуждение
[править код]- Есть же фреймворки для шарпа, зачем через голое API работать по харкдору? :) Из кода обычно интересны только сами замены. ~Facenapalm (обс.) 18:44, 26 июля 2017 (UTC)
- Когда-то искал библиотеки да и не нашел того, что требовалось. На этот раз же использовал прошлые наработки + дописывал по аналогии. Из преимуществ такого подхода: полный контроль над процессом работы бота; проще находить и исправлять ошибки. Вот замены: WikiParse/Program.cs. — Vort (обс.) 19:06, 26 июля 2017 (UTC)
- @Vort: погоди, я правильно понимаю, что алгоритм такой: а) скачать текст всех статей; б) отредактировать все статьи; в) сохранить всю пачку? Конфликтов правок не боишься? ~Facenapalm (обс.) 12:26, 28 июля 2017 (UTC)
- @Facenapalm: Скачиваются только те, которые в этом списке. Скачиваются вместе с датой последнего редактирования. В случае конфликта в базе ставится пометка и замена пропускается. — Vort (обс.) 12:28, 28 июля 2017 (UTC)
- Ну да, я имел в виду «все статьи из списка». :) Просто обычно боты работают по принципу «скачал одну статью — отредактировал одну статью — сохранил одну статью». Если нужен предварительный анализ, скажем, для составления списка статей, используется дамп. Что конфликтов правок бот устраивать не будет — это хорошо, но всё же, по-моему, путь это сложный и ненужный. Но дело твоё, конечно. ~Facenapalm (обс.) 12:33, 28 июля 2017 (UTC)
- Пакетная обработка всегда быстрее. К примеру, в случае скачки, будет качаться 50 статей за раз. Это позволит сэкономить время минимум 50-ти ping-ов. В случае обработки будут использоваться все 4 ядра моего процессора. — Vort (обс.) 12:37, 28 июля 2017 (UTC)
- Также по всем данным сразу можно собирать статистику и делать визуальный контроль. Отладка алгоритма тоже упрощается — пересчёт данных не требует их повторной закачки. — Vort (обс.) 13:33, 28 июля 2017 (UTC)
- С дампами не работал, но, как я понимаю, они намного больше той выборки, которая мне требуется. Да и данные не самые свежие — то есть всё равно надо докачивать обновившиеся версии. — Vort (обс.) 13:33, 28 июля 2017 (UTC)
- Ну да, я имел в виду «все статьи из списка». :) Просто обычно боты работают по принципу «скачал одну статью — отредактировал одну статью — сохранил одну статью». Если нужен предварительный анализ, скажем, для составления списка статей, используется дамп. Что конфликтов правок бот устраивать не будет — это хорошо, но всё же, по-моему, путь это сложный и ненужный. Но дело твоё, конечно. ~Facenapalm (обс.) 12:33, 28 июля 2017 (UTC)
- @Facenapalm: Скачиваются только те, которые в этом списке. Скачиваются вместе с датой последнего редактирования. В случае конфликта в базе ставится пометка и замена пропускается. — Vort (обс.) 12:28, 28 июля 2017 (UTC)
- @Vort: погоди, я правильно понимаю, что алгоритм такой: а) скачать текст всех статей; б) отредактировать все статьи; в) сохранить всю пачку? Конфликтов правок не боишься? ~Facenapalm (обс.) 12:26, 28 июля 2017 (UTC)
- Когда-то искал библиотеки да и не нашел того, что требовалось. На этот раз же использовал прошлые наработки + дописывал по аналогии. Из преимуществ такого подхода: полный контроль над процессом работы бота; проще находить и исправлять ошибки. Вот замены: WikiParse/Program.cs. — Vort (обс.) 19:06, 26 июля 2017 (UTC)
- Такая периодическая замена нужна, т.к. постоянно возникают ошибки, копируются из статьи в статью и на других языках из других хх-вики. Когда "там" вики-текст работает, а "тут" почему-то нет - это наверняка несёт негатив редким редакторам. :) ~Sunpriat (обс.) 20:53, 26 июля 2017 (UTC)
Итог
[править код]Вроде все нормально, флаг присвоен rubin16 (обс.) 19:38, 27 августа 2017 (UTC)