Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Извличане на спечелени европейски проекти/конкурси #48

Merged
merged 1 commit into from
Aug 8, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,7 @@ data_mining/downloader-mp-votes/error.log
data_mining/downloader-mp-votes/downloader.log

data_mining/session-videos/client_secrets.json

data_mining/eu-tenders/model/*.bak
data_mining/eu-tenders/output/*
!data_mining/eu-tenders/output/.keep
72 changes: 72 additions & 0 deletions data_mining/eu-tenders/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@

## База данни
```sql
--- create database
CREATE SCHEMA `eu-tenders` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci ;
--- grant access
GRANT ALL ON `eu-tenders`.* TO liolio@localhost ;
```

При разработката използвах _MySQL_ със [sequelize][3], конфигурацията се намира във `model/models.js`

Причината да използвам реална база данни, вместо текстов файл:

- гарантирам уникалност на записите създавани при _scrape_ на проект
- не държа всички данни в паметта по време на изпълнение на скрипта


## Команди
```bash
# print out all available commands
node app.js -h
# run all tests
npm test
# run a single test (npm install -g mocha)
mocha -g 'scrape project page'
# run the script
npm start
# run the admin (npm install -g express-admin)
# user: admin, pass: 11aaAA
admin x-admin/
```

## Модел
![eu-tenders][2]


## Етап 1
Обхождам [списъка с проекти][1], за да взема техните `id`та.

Освен това попълвам и _Място на изпълнение_ `place`, тъй като е форматирано добре. Отделните компоненти са разделени с `;`

Взимам и _Продължителност месеци_ `duration`, тъй като не намерих тази извадка на страницата за проект

Този етап е бавен тъй като техният сървър прави заявка към базата за всички записи. Освен това тегленето на страници е последователно, тъй като се използва `__VIEWSTATE` от предходната. Отне 2 часа и 15 минути за 1150 заявки/страници


## Етап 2
Правя заявка за всеки `project` запис в базата, за да попълня липсващата информация.

Този етап е по-бърз, тъй като заявката на техният сървър отнема по-малко време, и дърпам по 5 проекта паралелно. Отне 45 минути за 11465 заявки


## Изпълнение
1. Създаване на базата по някакъв начин, виж командите в началото
2. Инсталация
```bash
cd eu-tenders
npm install
```
3. Синхронизация на моделите с базата
```bash
npm app.js -s
```
4. Стартиране на скрипта
```bash
npm start
```


[1]: http://umispublic.government.bg/prProcedureProjectsInfo.aspx?op=-1&proc=-2&clear=1
[2]: http://i.imgur.com/ZqSjLA8.png
[3]: https://github.com/sequelize/sequelize
51 changes: 51 additions & 0 deletions data_mining/eu-tenders/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@

require('colors');
var spawn = require('child_process').spawn;
var async = require('async'),
cmd = require('commander');

var eu = require('./lib/index')();
var models = require('./model/models');

cmd.version('1.0.0')
.option('-l, --list', 'Scrape projects list')
.option('-p, --project', 'Scrape each individual project')
.option('-t, --truncate', 'Truncate all tables')
.option('-s, --sync', 'Sync all defined DAOs to the DB')
.parse(process.argv);



function truncate (done) {
if (!cmd.truncate) return done();
async.each(['Project', 'Contractor', 'Program', 'Executors', 'Partners'], function (name, done) {
models[name].destroy({}, {truncate:true})
.success(function () {done()})
.error(function (err) {console.log(err); done()});
}, done);
}

function done (action) {
console.log(action.cyan, 'DONE!'.rainbow);
process.exit();
}


if (cmd.list) {
eu.scrape.projects.all(done.bind(null,'list'));
}
else if (cmd.project) {
eu.scrape.project.all(done.bind(null,'projects'));
}
else if (cmd.list && cmd.project) {
async.series([
eu.scrape.projects.all,
eu.scrape.project.all
], done.bind(null,'list & projects'));
}
else if (cmd.truncate) {
truncate(done.bind(null,'truncate'));
}
else if (cmd.sync) {
spawn('node', ['model/models.js']).on('exit', done.bind(null,'sync'));
}
9 changes: 9 additions & 0 deletions data_mining/eu-tenders/lib/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

exports = module.exports = function () {
return {
request: require('./request'),
parse: require('./parse'),
scrape: require('./scrape'),
w: require('./write')
};
};
130 changes: 130 additions & 0 deletions data_mining/eu-tenders/lib/parse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@

var cheerio = require('cheerio'),
moment = require('moment');


exports.projects = {
total: function (html) {
var $ = cheerio.load(html);

return {
viewstate: $('#__VIEWSTATE').val(),
total: parseInt($('#ContentPlaceHolder1_lblMaxRows')
.text().trim().replace('Общ брой: ',''))
};
},
page: function (html) {
var $ = cheerio.load(html);

var projects = [];
$('.InfoTableProposal tr').each(function (index) {
if (!index) return;
projects.push({
id: parseInt($('td',this).eq(3).find('a').attr('href').replace(/.*id=(\d+).*/,'$1')),
place: $('td',this).eq(2).text().trim(),
duration: parseFloat($('td',this).eq(8).text().trim())
});
});

return {
viewstate: $('#__VIEWSTATE').val(),
projects: projects
};
}
};


exports.project = {
page: function (html) {
var $ = cheerio.load(html);
var project = {}, ref = {beneficiary:null, program:null, partners:[], executors: []};

function span (ctx, row) {
return $('tr',ctx).eq(row).find('td').eq(1).find('span');
}
function a (ctx, row) {
return $('tr',ctx).eq(row).find('td').eq(1).find('a');
}

(function identification (ctx) {
project.isun = span(ctx,1).text().trim();
project.number = span(ctx,2).text().trim();
project.number = (project.number == '---') ? null : project.number;
project.name = span(ctx,3).text().trim();

ref.beneficiary = {
id: parseInt(a(ctx,4).attr('href').replace(/.*benef=(\d+).*/,'$1')),
name: a(ctx,4).text().trim()
};
project.beneficiary_id = ref.beneficiary.id;

ref.program = {
id: parseInt(a(ctx,5).attr('href').replace(/.*op=(\d+).*/,'$1')),
name: a(ctx,5).text().trim(),
source: span(ctx,5).text().trim().replace('==>','').trim()
};
project.program_id = ref.program.id;

project.date_contract = moment(span(ctx,6).text().trim(), 'DD.MM.YYYY').format('YYYY-MM-DD');
project.date_begin = moment(span(ctx,7).text().trim(), 'DD.MM.YYYY').format('YYYY-MM-DD');
project.date_end = moment(span(ctx,8).text().trim(), 'DD.MM.YYYY').format('YYYY-MM-DD');

project.status = span(ctx,9).text().trim();
}($('#ContentPlaceHolder1_divIdentification')));


(function description (ctx) {
project.description = span(ctx,1).html().trim();
project.activities = span(ctx,2).html().trim();
}($('#ContentPlaceHolder1_divDescription')));


(function partners (ctx) {
if (!$('table', ctx).length) return;
$('tr', ctx).each(function (index) {
ref.partners.push({
id:parseInt($('a', this).attr('href').replace(/.*benef=(\d+).*/,'$1')),
name:$('a', this).text().trim()
});
});
}($('#ContentPlaceHolder1_tdPartners')));


(function executors (ctx) {
if (!$('table', ctx).length) return;
$('tr', ctx).each(function (index) {
ref.executors.push({
id:parseInt($('a', this).attr('href').replace(/.*benef=(\d+).*/,'$1')),
name:$('a', this).text().trim()
});
});
}($('#ContentPlaceHolder1_tdExecutors')));


(function financial (ctx) {
project.budget_approved = parseInt(span(ctx,1).text().trim().replace(' BGN','').replace(' ','')||0);
project.budget_total = parseInt(span(ctx,2).text().trim().replace(' BGN','').replace(' ','')||0);
project.budget_bfp_total = parseInt(span(ctx,3).text().trim().replace(' BGN','').replace(' ','')||0);
project.budget_paid = parseInt(span(ctx,4).text().trim().replace(' BGN','').replace(' ','')||0);

var bfp = null;

bfp = $('#ContentPlaceHolder1_tdBFP_EU_AssumedAmount').text().trim().replace(' ','');
project.budget_bfp_eu = bfp ? parseInt(bfp||0) : 0;

bfp = $('#ContentPlaceHolder1_tdBFP_National_AssumedAmount').text().trim().replace(' ','');
project.budget_bfp_nat = bfp ? parseInt(bfp||0) : 0;

bfp = $('#ContentPlaceHolder1_tdBenef_AssumedAmount').text().trim().replace(' ','');
project.budget_benef = bfp ? parseInt(bfp||0) : 0;
}($('#ContentPlaceHolder1_divFinansicalInfo')));


(function indicators (ctx) {

}($('#ContentPlaceHolder1_divIndicators')));


return {project:project, ref:ref};
}
};
52 changes: 52 additions & 0 deletions data_mining/eu-tenders/lib/request.js

Large diffs are not rendered by default.

Loading