Skip to content

Commit cf8c658

Browse files
authored
Merge pull request #2 from gruppin/fix-tel-aviv-trees
2 parents f3e4f71 + 1a120be commit cf8c658

3 files changed

Lines changed: 152 additions & 44 deletions

File tree

client/.env

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
REACT_APP_MAPBOX_KEY=pk.eyJ1IjoibWVpcmltIiwiYSI6ImNrbWNjYmlwYjJhYzYycW42NTYzcmdpYWcifQ.lCBjl1gWMI6UxySOCCBUHg
2-
REACT_APP_BASE_API_URL=https://change-this.meirim.org/api
2+
REACT_APP_BASE_API_URL=http://localhost:3001

client/config/default.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"axios": {
3-
"baseURL": "https://staging-api.meirim.org/api",
3+
"baseURL": "http://localhost:3001",
44
"withCredentials": true
55
},
66
"mapboxApiKey": "pk.eyJ1IjoibWVpcmltIiwiYSI6ImNrbWNjYmlwYjJhYzYycW42NTYzcmdpYWcifQ.lCBjl1gWMI6UxySOCCBUHg",

server/api/lib/trees/tlv_tree_permit.js

Lines changed: 150 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
const proxy = require('./../proxy');
2-
const cheerio = require('cheerio');
31
const Config = require('../../lib/config');
42
const TreePermit = require('../../model/tree_permit');
3+
const puppeteer = require('puppeteer');
4+
55
const {
66
REGIONAL_OFFICE, PERMIT_NUMBER, APPROVER_TITLE, ACTION,
77
LAST_DATE_TO_OBJECTION, TOTAL_TREES,
@@ -34,43 +34,7 @@ const tlvTreePermit = {
3434
};
3535

3636
async function parseTreesHtml(url) {
37-
const treesHtml = await proxy.get(url);
38-
const dom = cheerio.load(treesHtml, {
39-
decodeEntities: false
40-
});
41-
if (!dom) {
42-
console.error('cheerio dom is null');
43-
}
44-
const result = [];
45-
46-
const rawRows = dom('.table-scrl').find('tr');
47-
//ignore row 0
48-
for (let i = 1; i < rawRows.length; i = i+2) {
49-
const permit = {};
50-
permit.permitNumber = dom(rawRows[i]).attr('title');
51-
dom(rawRows[i]).find('td').each((idx,elem) => {
52-
const val = dom(elem).text().trim();
53-
if (idx == 0) {
54-
permit[LICENSE_NUMBER] = val;
55-
}
56-
if (idx == 1) {
57-
permit[STREET_NAME] = val;
58-
}
59-
if (idx == 2) {
60-
permit[ACTION] = val;
61-
}
62-
});
63-
64-
65-
dom(rawRows[i+1]).find('td > div > div').each((idx,elem) => {
66-
const key = dom(elem).find('div h5').text().trim();
67-
const value = dom(elem).find('div span').text().trim();
68-
permit[key] = value;
69-
});
70-
71-
console.log(`tree permit tlv: ${Object.entries(permit)}`);
72-
result.push(permit);
73-
}
37+
const result = await scrapeTelAvivTreesRawRows(url);
7438
Log.info(`number of Tel Aviv permits: ${result.length}`);
7539
return result;
7640
}
@@ -104,14 +68,13 @@ function processRawPermits(rawPermits) {
10468
[REASON_DETAILED]: raw[LICENSE_REASON],
10569
[TREES_PER_PERMIT]: treesPerPermit,
10670
[TOTAL_TREES]: totalTrees,
107-
10871
[TREE_PERMIT_URL]: TREES_TEL_AVIV_URL,
10972
};
11073
const permit = new TreePermit(attributes);
11174
return permit;
11275
}
11376
catch (e) {
114-
Log.error(`error in hod hasharon parse row, ignoring: ${raw[STREET_NAME]}`, e.message);
77+
Log.error(`error in Tel Aviv parse row, ignoring: ${raw[STREET_NAME]}`, e.message);
11578
return null;
11679
}
11780
}
@@ -155,7 +118,7 @@ function sum(treeArray) {
155118
*/
156119
async function crawlTLVTrees(url, permitType) {
157120
try {
158-
const raw = await parseTreesHtml(url);
121+
const raw = await parseTreesHtml(TREES_TEL_AVIV_URL);
159122
const treePermits = processRawPermits(raw);
160123
return treePermits;
161124
}
@@ -164,4 +127,149 @@ async function crawlTLVTrees(url, permitType) {
164127
}
165128
}
166129

130+
131+
async function scrapeTelAvivTreesRawRows(url) {
132+
let browser;
133+
try {
134+
// Launch browser
135+
Log.info('Launching browser...');
136+
browser = await puppeteer.launch({
137+
headless: true,
138+
args: ['--no-sandbox']
139+
});
140+
141+
const page = await browser.newPage();
142+
// Listen to console messages from the page
143+
// page.on('console', msg => console.log('PAGE LOG:', msg.text()));
144+
145+
// Navigate to the page
146+
Log.info('Loading page...');
147+
await page.goto(url, {
148+
waitUntil: 'networkidle2',
149+
timeout: 50000
150+
});
151+
152+
// Wait for tables to appear
153+
Log.info('Waiting for content to load...');
154+
await page.waitForSelector('table', { timeout: 10000 });
155+
156+
// Additional wait using Promise
157+
await new Promise(resolve => setTimeout(resolve, 3000));
158+
159+
// Extract the table data
160+
Log.info('Extracting and parsing data...');
161+
const rows = await page.evaluate((LICENSE_NUMBER, STREET_NAME, ACTION, REASON_DETAILED, OBJECTION_TILL, TREE_NUM) => {
162+
try {
163+
const targetCaption = 'הודעות על אישור כריתה או העתקה של עצים';
164+
// Find the table with the caption
165+
const captions = Array.from(document.querySelectorAll('caption, h2, h3, h4, div, span'));
166+
let targetTable = null;
167+
168+
for (const elem of captions) {
169+
if (elem.textContent.includes(targetCaption)) {
170+
// Try to find the table (either parent or next sibling)
171+
targetTable = elem.closest('table') || elem.nextElementSibling;
172+
173+
// If next sibling isn't a table, look further
174+
if (targetTable && targetTable.tagName !== 'TABLE') {
175+
targetTable = elem.parentElement.querySelector('table');
176+
}
177+
178+
if (targetTable && targetTable.tagName === 'TABLE') {
179+
break;
180+
}
181+
}
182+
}
183+
184+
if (!targetTable) {
185+
return { error: 'Table not found' };
186+
}
187+
// Extract all rows
188+
const rawRows = Array.from(targetTable.querySelectorAll('tr'));
189+
const result = [];
190+
191+
// Parse rows in pairs
192+
for (let i = 1; i < rawRows.length; i = i + 2) {
193+
const permit = {};
194+
// Get permit number from title attribute
195+
permit.permitNumber = rawRows[i].getAttribute('title') || '';
196+
197+
// Parse first row (license number, street, action)
198+
const cells = rawRows[i].querySelectorAll('td');
199+
cells.forEach((elem, idx) => {
200+
const val = elem.textContent.trim();
201+
if (idx === 0) {
202+
permit[LICENSE_NUMBER] = val;
203+
}
204+
if (idx === 1) {
205+
permit[STREET_NAME] = val;
206+
}
207+
if (idx === 2) {
208+
permit[ACTION] = val;
209+
210+
}
211+
if (idx === 3) {
212+
permit[TREE_NUM] = val;
213+
}
214+
if (idx === 4) {
215+
permit[REASON_DETAILED] = val;
216+
}
217+
if (idx === 5) {
218+
permit[OBJECTION_TILL] = val;
219+
}
220+
});
221+
222+
// Parse second row (additional details in nested divs)
223+
// Parse second row - let's debug the structure
224+
// Parse second row - additional details
225+
if (i + 1 < rawRows.length) {
226+
const secondRow = rawRows[i + 1];
227+
const allH5s = secondRow.querySelectorAll('h5');
228+
229+
allH5s.forEach((h5Elem) => {
230+
const key = h5Elem.textContent.trim();
231+
let valueElem = h5Elem.nextElementSibling;
232+
if (!valueElem || valueElem.tagName !== 'SPAN') {
233+
valueElem = h5Elem.parentElement.querySelector('span');
234+
}
235+
if (!valueElem || valueElem.tagName !== 'SPAN') {
236+
valueElem = h5Elem.closest('div').querySelector('span');
237+
}
238+
239+
if (valueElem) {
240+
const value = valueElem.textContent.trim();
241+
permit[key] = value;
242+
}
243+
});
244+
}
245+
result.push(permit);
246+
247+
}
248+
249+
return result;
250+
}
251+
catch (error) {
252+
Log.error('Error:', error.message);
253+
return { error: error.message };
254+
}
255+
} ,LICENSE_NUMBER, STREET_NAME, ACTION, REASON_DETAILED, OBJECTION_TILL, TREE_NUM); // Pass constants to the browser context);
256+
257+
if (rows.error) {
258+
Log.error(rows.error);
259+
} else {
260+
Log.info('Found', rows.length, 'rows');
261+
Log.info(JSON.stringify(rows, null, 2));
262+
}
263+
264+
return rows;
265+
266+
} catch (error) {
267+
Log.error(error.message);
268+
} finally {
269+
if (browser) {
270+
await browser.close();
271+
}
272+
}
273+
}
274+
167275
module.exports = { crawlTLVTrees, tlvTreePermit: tlvTreePermit };

0 commit comments

Comments
 (0)