Загрузка данных с сайта в собственную базу

Допустим, вам надо перенести определенные данные с какого-то сайта в свою базу данных в красивом, структурированном виде. Но вы не хотите сидеть и копи-пастить в Excel эти данные. Тогда может помочь следующий метод. Он основан на использовании C# приложения для загрузки, очистки и парсинга HTML и дальнейшей загрузке данных в Excel программно.

Для пользования данным методом нужно владеть языком C#, так как мой код надо всегда затачивать под каждый сайт.

1. Открываем среду разработки на C#(Я пользуюсь MS Visual Studio 2010 Ultimate). Создаем новый проект с простой формой:

В адресе URL мы введем страницу, из которой нужно получить контент при нажатии на кнопку. При этом автоматически откроется Excel 2007 и в него занесутся данные – этот контент.

2. Нажимаем правой кнопкой по References под названием проекта в Solution Explorer. Выбираем Add Reference. Далее во вкладке COM находим и выбираем компоненту Microsoft Excel 12.0 Object Library. Ссылки на эту компоненту понадобятся для автоматической загрузки данных в Excel.

3. Редактируем список пространств имен:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Linq;
using System.Web;
using System.IO;
using System.Net;
using System.Collections;
using System.Collections.Specialized;
using System.Configuration;
using Excel = Microsoft.Office.Interop.Excel;
using System.Reflection;

4. Пишем код для кнопки.

private void button1_Click(object sender, EventArgs e)
{
string OldText, NewText, PossibleRefText, RefText, textOfCitation, authorOfCitation;
strURL = textBox1.Text;
OldText = GetHtmlFromUrl(strURL);
int begin, begin0, i;
int end;
int length;
int lastindex;
int newindex;
int indexOfClosingTag, difference, indexOfHref;
int indexofrefbegin, indexofspanbegin, indexOfAuthorEnd, indexOfAuthorBegin, indexOfAuthorBegin0;
RefText = "<a href";
newindex = OldText.IndexOf("<p class=\"data\">");
begin = OldText.IndexOf("<p class=\"data\">");
lastindex = OldText.LastIndexOf("<p class=\"data\">");
OldText = OldText.Remove(0, newindex);
i = 0;
while (!(newindex == (-1)))
{
i = i + 1;
begin0 = OldText.IndexOf("<p class=\"data\">");
begin = OldText.IndexOf("<p class=\"data\">") + 16;
indexofrefbegin = begin;
end = OldText.IndexOf("</p>");
length = end - begin;
if (length < 0)
{
break;
}
textOfCitation = OldText.Substring(begin, length);
OldText = OldText.Remove(0, end + 4);

indexOfAuthorBegin0 = OldText.IndexOf("class=\"author\"");
OldText = OldText.Remove(0, indexOfAuthorBegin0);
indexOfAuthorBegin0 = OldText.IndexOf("class=\"author\"");
indexOfHref = OldText.IndexOf("href=");
difference = indexOfHref - (indexOfAuthorBegin0 + 15);
if (difference < 5 && difference >= 0)
{
indexOfClosingTag = OldText.IndexOf(">");
length = indexOfClosingTag - indexOfAuthorBegin0 - 14;
OldText = OldText.Remove(indexOfAuthorBegin0 + 15, length);
}
indexOfAuthorBegin = OldText.IndexOf("class=\"author\"") + 15;

indexOfAuthorEnd = OldText.IndexOf("<");
length = indexOfAuthorEnd - indexOfAuthorBegin;
if (length < 0)
{
break;
}

authorOfCitation = OldText.Substring(indexOfAuthorBegin, length);
OldText = OldText.Remove(0, indexOfAuthorEnd + 1);

//Literal1.Text = Literal1.Text + " " + i.ToString() + ". " + textOfCitation + " " + "(" + authorOfCitation + ")" + "</br>";
textOfCitation = cleanFromBr(textOfCitation);
textOfCitation = cleanFromQuot(textOfCitation);
citations[i - 1, 0] = textOfCitation;
citations[i - 1, 1] = authorOfCitation;

}

//Добавляем массив цитат в Excel
InsertToExcel();

//  движок для википедии
//{
//    string OldText, NewText, PossibleRefText, RefText;
//    strURL = TextBox1.Text;
//    OldText = GetHtmlFromUrl(strURL);
//    int begin;
//    int end;
//    int length;
//    int lastindex;
//    int newindex;
//    int indexofrefbegin, indexofspanbegin;
//    RefText = "<p class=\"data\">";
//    newindex = OldText.IndexOf(RefText);
//    begin = OldText.IndexOf(RefText);
//    lastindex = OldText.LastIndexOf("</p>");
//    while (!(newindex == (-1)))
//    {
//        begin = OldText.IndexOf(RefText) + 16;
//        end = OldText.IndexOf("</p>");
//        length = end - begin;
//        NewText = OldText.Substring(begin, length);
//        newindex = OldText.IndexOf(RefText);
//        Literal1.Text = Literal1.Text + NewText;
//    }
//}

}

Здесь мы сначала запрашиваем HTML в текстовую строку с помощью функции GetHtmlFromUrl. Затем мы эту текстовую строку обрабатываем, удаляя все ненужное, а нужное занося в массив. Затем добавляем получившийся массив в Excel с помощью функции InsertToExcel().

5. Пишем дополнительный вспомогательный код с функциями, которые использовались в коде для кнопки:


private string cleanFromBr(string text)
{
int indexOfStart, indexOfEnd;
indexOfStart = text.IndexOf("<br/>") - 1;
while (indexOfStart != -2)
{
indexOfEnd = indexOfStart + 6;
text = text.Remove(indexOfStart, 6);
text = text.Insert(indexOfStart, " ");
indexOfStart = text.IndexOf("<br/>") - 1;
}
return text;
}

private string cleanFromQuot(string text)
{
int indexOfStart, indexOfEnd;
indexOfStart = text.IndexOf("&quot;");
while (indexOfStart != -1)
{
indexOfEnd = indexOfStart + 6;
text = text.Remove(indexOfStart, 6);
text = text.Insert(indexOfStart, "\"");
indexOfStart = text.IndexOf("&quot;");
}
return text;
}

private void InsertToExcel()
{
Excel.Application oXL;
Excel._Workbook oWB;
Excel._Worksheet oSheet;
Excel.Range oRng;

try
{
//Start Excel and get Application object.
oXL = new Excel.Application();
oXL.Visible = true;

//Get a new workbook.
oWB = (Excel._Workbook)(oXL.Workbooks.Add(Missing.Value));
oSheet = (Excel._Worksheet)oWB.ActiveSheet;

//Add table headers going cell by cell.
//oSheet.Cells[1, 1] = "First Name";
//oSheet.Cells[1, 2] = "Last Name";
//oSheet.Cells[1, 3] = "Full Name";
//oSheet.Cells[1, 4] = "Salary";

//Format A1:D1 as bold, vertical alignment = center.
//oSheet.get_Range("A1", "D1").Font.Bold = true;
//oSheet.get_Range("A1", "D1").VerticalAlignment =
//    Excel.XlVAlign.xlVAlignCenter;

//Fill A1:B20 with an array of values (First and Last Names).
oSheet.get_Range("B1", "C20").Value2 = citations;

////Fill C2:C6 with a relative formula (=A2 & " " & B2).
//oRng = oSheet.get_Range("C2", "C6");
//oRng.Formula = "=A2 & \" \" & B2";

////Fill D2:D6 with a formula(=RAND()*100000) and apply format.
//oRng = oSheet.get_Range("D2", "D6");
//oRng.Formula = "=RAND()*100000";
//oRng.NumberFormat = "$0.00";

////AutoFit columns A:D.
//oRng = oSheet.get_Range("A1", "D1");
//oRng.EntireColumn.AutoFit();

//Manipulate a variable number of columns for Quarterly Sales Data.
//DisplayQuarterlySales(oSheet);

//Make sure Excel is visible and give the user control
//of Microsoft Excel's lifetime.
oXL.Visible = true;
oXL.UserControl = true;
}
catch (Exception theException)
{
String errorMessage;
errorMessage = "Error: ";
errorMessage = String.Concat(errorMessage, theException.Message);
errorMessage = String.Concat(errorMessage, " Line: ");
errorMessage = String.Concat(errorMessage, theException.Source);

//MessageBox.Show(errorMessage, "Error");
}
}

public enum ResponseCategories
{
Unknown = 0,       // Unknown code ( < 100 or > 599)
Informational = 1, // Informational codes (100 >= 199)
Success = 2,       // Success codes (200 >= 299)
Redirected = 3,    // Redirection code (300 >= 399)
ClientError = 4,   // Client error code (400 >= 499)
ServerError = 5    // Server error code (500 >= 599)
}

public static string GetHtmlFromUrl(string url)
{
if (string.IsNullOrEmpty(url))
throw new ArgumentNullException("url", "Parameter is null or empty");

string html = "";
HttpWebRequest request = GenerateHttpWebRequest(url);
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
if (VerifyResponse(response) == ResponseCategories.Success)
{
// Get the response stream.
Stream responseStream = response.GetResponseStream();
// Use a stream reader that understands UTF8.
using (StreamReader reader =
new StreamReader(responseStream, Encoding.UTF8))
{
html = reader.ReadToEnd();
}
}
}
return html;
}

public static HttpWebRequest GenerateHttpWebRequest(string UriString)
{
// Get a Uri object.
Uri Uri = new Uri(UriString);
// Create the initial request.
HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(Uri);
// Return the request.
return httpRequest;
}

// POST overload
public static HttpWebRequest GenerateHttpWebRequest(string UriString,
string postData,
string contentType)
{
// Get a Uri object.
Uri Uri = new Uri(UriString);
// Create the initial request.
HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(Uri);

// Get the bytes for the request; should be pre-escaped.
byte[] bytes = Encoding.UTF8.GetBytes(postData);

// Set the content type of the data being posted.
httpRequest.ContentType = contentType;
//"application/x-www-form-urlencoded"; for forms

// Set the content length of the string being posted.
httpRequest.ContentLength = postData.Length;

// Get the request stream and write the post data in.
using (Stream requestStream = httpRequest.GetRequestStream())
{
requestStream.Write(bytes, 0, bytes.Length);
}
// Return the request.
return httpRequest;
}

public static ResponseCategories VerifyResponse(HttpWebResponse httpResponse)
{
// Just in case there are more success codes defined in the future
// by HttpStatusCode, we will check here for the "success" ranges
// instead of using the HttpStatusCode enum as it overloads some
// values.
int statusCode = (int)httpResponse.StatusCode;
if ((statusCode >= 100) && (statusCode <= 199))
{
return ResponseCategories.Informational;
}
else if ((statusCode >= 200) && (statusCode <= 299))
{
return ResponseCategories.Success;
}
else if ((statusCode >= 300) && (statusCode <= 399))
{
return ResponseCategories.Redirected;
}
else if ((statusCode >= 400) && (statusCode <= 499))
{
return ResponseCategories.ClientError;
}
else if ((statusCode >= 500) && (statusCode <= 599))
{
return ResponseCategories.ServerError;
}
return ResponseCategories.Unknown;
}

Часть этих функций нужна, чтобы запросить HTML, часть чтобы очистить его от тэгов. Набор этих функций, как и код кнопки будут меняться в зависимости от того, с какого именно сайта вы хотите получить контент. То есть данный метод не универсален, его надо затачивать под каждый сайт. Я не буду подробно разъяснять этот код, так как все равно нужно обладать хорошими знаниями в C#, чтобы применять данный метод. Кроме этого, тут есть неплохие комментарии.

4. Здесь http://support.microsoft.com/kb/302084 описано, как писать код для загрузки данных в Excel.

5. Далее, как я описал ранее, можно выгрузить данные из Excel в XML, а XML уже загрузить туда, куда вам надо. Смотрите предыдущий пост в этом блоге.

Метод наполнения базы SQLite через выгрузку Excel данных в XML

Здесь будет описан метод, который позволяет наполнять базу данных SQLite из Excel посредством выгрузки данных в XML.

SQLite — легковесная встраиваемая реляционная база данных. Исходный код библиотеки передан в общественное достояние. В 2005 году проект получил награду Google-O’Reilly Open Source Awards. Простота и удобство встраивания SQLite привели к тому, что библиотека используется в браузерах, музыкальных плеерах и многих других программах. Многие программы поддерживают SQLite в качестве формата хранения данных (особенно в Mac OS и iPhone OS, Android).

Можно использовать Microsoft Excel 2007, на других версиях метод не тестировался. Предположим, есть заполненная данными таблица Excel 2007 с четырьмя столбцами: ID, Text, Author, Favorite.

1. Создаем XML файл в Windows – образец будущего большого XML файла. Он будет играть роль схемы в Excel. Например,

<?xml version=”1.0″ encoding=”UTF-8″ standalone=”yes”?>
<BusCits xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”>
<BusCit>
<ID>1</ID>
<Text>Т1</Text>
<Author>А1</Author>
<Favorite>N</Favorite>
</BusCit>
<BusCit>
<ID>2</ID>
<Text>Т2</Text>
<Author>А2</Author>
<Favorite>N</Favorite>
</BusCit>
</BusCits>

Здесь представлены два элемента BusCit, в каждом из которых есть параметры ID, Text, Author, Favorite. Элементы являются дочерними по отношению к главному элементу BusCits. Сохраняем этот образцовый XML. Назовем его XMLSchema.xml.

2. В Excel 2007 в Ribbon’е есть вкладка Разработчик. Открываем ее. На этой вкладке есть раздел XML. Нажимаем там на кнопку “Источик”, а затем в появившейся справа панели на кнопку “Карты XML…”. Нажимаем Добавить и добавляем ранее сохраненный XMLSchema.xml. Нажимаем Ок. Соглашаемся использовать маленький XML в качестве схемы. Дальше в той же правой панели Источник XML должна появиться ваша схема. Нажимаем правой кнопкой на ID и выбираем Сопоставить элемент. Далее выбираем первый столбец в таблице с ID, нажав на букву, которая его идентифицирует, при этом заголовки тоже выделяются(A, B, C и т.д). Аналогично поступаем с каждым другим параметром: Text, Author, Favorite.  Мы привязали XML схему к таблице, теперь во вкладке Разработчик/XML в Ribbon нажимаем Export и экспортируем XML с нужным нам названием, например BusCits.xml. Готово, получившийся XML можно удобно посмотреть в программе Notepad++ и проверить. Должно получиться что-то вроде этого:

3. Запускаем Mac Os X. Заходим в XCode и, используя сайт http://www.tbxml.co.uk и  библиотеку, описанную в нем, пишем код для парсинга XML файла. Можно использовать любую другую облегченную библиотеку XML.
Пример работающего кода:


-(void) insertBusCitsToDatabase {
// Setup the database object
sqlite3 *database;
char *sqlStatement;
NSString *insert;
//Подготовка к парсингу XML
TBXML * tbxml = [[TBXML tbxmlWithXMLFile:@"BusCits.xml"] retain];
//Получение корневого элемента
TBXMLElement * rootXMLElement = tbxml.rootXMLElement;
TBXMLElement * XMLBusCit;
TBXMLElement * XMLID;
TBXMLElement * XMLText;
TBXMLElement * XMLAuthor;
TBXMLElement * XMLFavorite;
NSString *BusCitText;
NSString *BusCitId;
NSString *BusCitAuthor;
NSString *BusCitFavorite;
char *errMsg;
int returnValue;

// Init the busCits Array
busCits = [[NSMutableArray alloc] init];

//Cycle until XML ended
//1. Read XML to busCit object
//2. Insert busCit object to DB
//End Cycle

// Open the database from the users filessytem
if(sqlite3_open([databasePath UTF8String], &database) == SQLITE_OK) {

XMLBusCit = [TBXML childElementNamed:@"BusCit" parentElement:rootXMLElement];
if (XMLBusCit==nil) {
return;
}

while (XMLBusCit) {

if (XMLBusCit==nil) {
break;
}
XMLID = [TBXML childElementNamed:@"ID" parentElement:XMLBusCit];
XMLText = [TBXML childElementNamed:@"Text" parentElement:XMLBusCit];
XMLAuthor = [TBXML childElementNamed:@"Author" parentElement:XMLBusCit];
XMLFavorite = [TBXML childElementNamed:@"Favorite" parentElement:XMLBusCit];
BusCitText = [TBXML textForElement:XMLText];
BusCitId = [TBXML textForElement:XMLID];
BusCitAuthor = [TBXML textForElement:XMLAuthor];
BusCitFavorite = [TBXML textForElement:XMLFavorite];

NSLog(BusCitId);
NSLog(BusCitText);
NSLog(BusCitAuthor);
NSLog(BusCitFavorite);
sqlite3_stmt *insertStatement;
char *st;

//Генерация и подготовка SQL выражения для вставки записи

insert = [[NSString alloc] initWithFormat:@"INSERT OR REPLACE INTO BusCits (ID, Text, Author, Favorite) VALUES ('%@', '%@', '%@', '%@');", BusCitId, BusCitText, BusCitAuthor, BusCitFavorite];
st = [insert UTF8String];
//NSLog(st);
returnValue = sqlite3_exec (database, st, NULL, NULL, &errMsg);
if (returnValue != SQLITE_OK)
{
NSLog(@"Addition failed");
//NSLog(errMsg);
}
else
{
NSLog(@"Addition Ok!");

}
XMLBusCit = XMLBusCit->nextSibling;
}

// Setup the SQL Statement and compile it for faster access
const char *sqlStatement = "SELECT * FROM BusCits";
sqlite3_stmt *compiledStatement;
if(sqlite3_prepare_v2(database, sqlStatement, -1, &compiledStatement, NULL) == SQLITE_OK)
{
// Loop through the results and add them to the feeds array
while(sqlite3_step(compiledStatement) == SQLITE_ROW)
{
// Read the data from the result row
NSString *aId = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 1)];
NSString *aText = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 2)];
NSString *aAuthor = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 3)];
NSString *aFavorite = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 4)];

// Create a new animal object with the data from the database
BusCit *busCit = [[BusCit alloc] initWithID:aId Text:aText Author:aAuthor Favorite:aFavorite];
// Add the animal object to the animals Array
[busCits addObject:busCit];

[busCit release];
}
}
// Release the compiled statement from memory
sqlite3_finalize(compiledStatement);

}
sqlite3_close(database);

}

Для того, чтобы посмотреть и проверить работу используется NSLog. После отработки функции можно нажать Shift+Command+R и вызвать консоль, в которой пишется то, что в NSLog.

О runtime ошибке SIGABRT

Баг с текстом SIGABRT в NSLog может появляться при тестировании программы на iPhone Simulator. Например, при открытии нового View. То есть он возникает во время работы программы, а компилятор XCode не находит эту ошибку. На понимание причины бага или сигнала SIGABRT было потрачено 3-4 часа. Поэтому было решено написать эту статью. Посмотрим сначала, что нам пишет википедия об этой ошибке.

POSIX® (англ. Portable Operating System Interface for Unix — Переносимый интерфейс операционных систем Unix) — набор стандартов, описывающих интерфейсы между операционной системой и прикладной программой. Стандарт создан для обеспечения совместимости различных UNIX-подобных операционных систем и переносимости прикладных программ на уровне исходного кода, но может быть использован и для не-Unix систем.
В POSIX-системах, SIGABRT — сигнал, посылаемый процессом самому себе при выполнении функции abort(), для аварийного останова, в случае невозможности дальнейшего продолжения программы. Сигнал SIGABRT процесс посылает сам себе, когда вызывает функцию abort() (функция libc, с прототипом в stdlib.h). Сигнал SIGABRT может быть перехвачен или проигнорирован, но abort() принудительно завершит программу (предварительно сбросив и закрыв стандартные потоки и сделав дамп памяти программы) даже если этого не сделает обработчик сигнала. Таким образом, функция abort() гарантирует завершение программы, почему она часто используется библиотеками в аварийных ситуациях — когда выполнение не должно быть продолжено, но основная программа должна иметь возможность выполнить действия, необходимые перед аварийным завершением. Также SIGABRT вызвается утверждениями (англ. assertions; средство отладки Си — макроопределение assert() вызывает abort() если утверждение переданное в качестве аргумента ложно).

Из этого текста можно понять, что ошибка или сигнал SIGABRT посылается просто из-за вызова функции abort() при падении программы. В свою очередь эта функция может вызываться утверждением assert(), но мы не можем посмотреть и выявить точную причину бага. Поэтому SIGABRT – один из самых вредных и труднопреодолимых багов при разработке для iPhone: отладка(debugging) не помогает, так как ошибка возникает где-то в коде, который не доступен разработчику. В интернете можно прочитать много всего про этот баг приложений для iPhone. Кому-то помогла перезагрузка XCode каким-то магическим образом или “Clean all Targets”. Пишут также, что это ошибка XCode или Симулятора, а не ошибка разработчика. Я думаю, что проблема в другом.

Наверное, главная и самая распространенная причина этой ошибки(касательно iPhone, iPhone Simulator) в том, что XIB не соответствует вашим аутлетам(Outlets) или классам. Например, если написать:
@interface FavoritesTableView : UITableViewController,

когда у вас нет контроллера TableViewController, а вместо него обычный View с контролом TableView, то выскочит эта ошибка. Видимо, программа пытается обратиться к сущностям, которых нет, при загрузке View. Они либо были освобождены из памяти(deallocated) раньше времени, либо их просто нет (например, в XIB). Далее вызывается abort(), который посылает сигнал SIGABRT.

Итак, в данном случае надо было написать:
@interface FavoritesTableView : UIViewController

Также SIGABRT может быть вызван обращением к любой несуществующей сущности – к элементу за пределами массива, к переменной, объекту, XIB файлу и т.д.