четверг, 2 августа 2012 г.

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

На сайте Россвязи есть замечательный, периодически обновляющийся список, содержащий номерные ёмкости и телефонные компании, к которым эти диапазоны относятся. В данной статье описывается процедура получения и разбора этого файла с помощью апачевского HttpClient и обычного SAX парсера.

  • Скачиваем html со списком кодов

Для этого вполне подойдет org.apache.http.impl.client.DefaultHttpClient

DefaultHttpClient client = new DefaultHttpClient();
HttpGet httpget = new HttpGet("http://rossvyaz.ru/docs/articles/DEF-9x.html");

HttpResponse resp = client.execute(httpget);
if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
    InputStream is = resp.getEntity().getContent();
    List registry = new ArrayList();
    parseHTML(is, registry);
}

Но вот незадача, контент сжат gzip'ом. В принципе это даже неплохо, уменьшается время загрузки и сетевой трафик, так что просто добавим поддержку gzip в HttpClient. Делается это путем добавления перехватчиков на запрос и ответ:

private DefaultHttpClient prepareHttpClient() {
        DefaultHttpClient client = new DefaultHttpClient();
        client.addRequestInterceptor(new HttpRequestInterceptor() {

            public void process(
                    final HttpRequest request,
                    final HttpContext context) throws HttpException, IOException {
                if (!request.containsHeader("Accept-Encoding")) {
                    request.addHeader("Accept-Encoding", "gzip");
                }
            }

        });

        client.addResponseInterceptor(new HttpResponseInterceptor() {
            public void process(
                    final HttpResponse response,
                    final HttpContext context) throws HttpException, IOException {
                HttpEntity entity = response.getEntity();
                if (entity != null) {
                    Header ceheader = entity.getContentEncoding();
                    if (ceheader != null) {
                        HeaderElement[] codecs = ceheader.getElements();
                        for (int i = 0; i < codecs.length; i++) {
                            if (codecs[i].getName().equalsIgnoreCase("gzip")) {
                                response.setEntity(
                                        new GzipDecompressingEntity(response.getEntity()));
                                return;
                            }
                        }
                    }
                }
            }

        });
        return client;
    }

Теперь осталось разобрать полученный html и вытащить из него DEF коды.

  • Парсим полученный html

Воспользуется стандартным потоковым java SAX парсером (javax.xml.stream.XMLEventReader), поскольку полученный файл большой, и к тому же является невалидным XML файлом, так что придется произвести небольшие ухищрения, чтобы его разобрать. Разбор производится "на лету", то есть начинается уже в процессе скачивания html, за счет чего достигается приличная скорость при небольшом потреблении памяти. Весь процесс занимает порядка трех секунд.


static final String TR = "tr";
static final String TD = "td";

...

private void parseHTML(InputStream is, List<DEFCode> result) {
        try {
            XMLInputFactory inputFactory = XMLInputFactory.newInstance();
            XMLEventReader eventReader = inputFactory.createXMLEventReader(is, "windows-1251");
            String[] buff = new String[6];
            int count = 0;
            int ind = 0;
            while (eventReader.hasNext()) {
                try {
                    XMLEvent event = eventReader.nextEvent();

                    if (event.isStartElement()) {
                        StartElement startElement = event.asStartElement();
                        // start new row
                        if (TR.equals(startElement.getName().getLocalPart())) {
                            buff = new String[6];
                            count++;
                            ind = 0;
                        }

                        if (TD.equals(event.asStartElement().getName().getLocalPart())) {
                            event = eventReader.nextEvent();
                            buff[ind++] = event.asCharacters().getData();
                            continue;
                        }
                    }

                    if (event.isEndElement()) {
                        EndElement endElement = event.asEndElement();
                        if (TR.equals(endElement.getName().getLocalPart())) {
                            if(count != 1){ // пропускаем первую строку с заголовком
                                result.add(validateRow(count, buff));
                            }
                        }
                    }
                } catch (XMLStreamException e) { // вероятнее всего это незакрытый тег, игнорируем ошибку
                    logger.error("skip error");
                } catch(Exception ex){ // вероятнее всего файл просто закончился, завершаем обработку
                    logger.error("skip error, break");
                    break;
                }
            }
            eventReader.close();

        } catch (XMLStreamException e) { // что-то не так с кодировкой или структурой файла
            e.printStackTrace();
        }
    }

private DEFCode validateRow(int counter, String[] nextLine) throws ValidationException {
        // здесь проводится проверка строки на валидность и если 
        // строка не валидна, выбрасывается эксепшн с номером строки 
    }

Комментариев нет:

Отправить комментарий