1- const proxy = require ( './../proxy' ) ;
2- const cheerio = require ( 'cheerio' ) ;
31const Config = require ( '../../lib/config' ) ;
42const TreePermit = require ( '../../model/tree_permit' ) ;
3+ const puppeteer = require ( 'puppeteer' ) ;
4+
55const {
66 REGIONAL_OFFICE , PERMIT_NUMBER , APPROVER_TITLE , ACTION ,
77 LAST_DATE_TO_OBJECTION , TOTAL_TREES ,
@@ -34,43 +34,7 @@ const tlvTreePermit = {
3434} ;
3535
3636async 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 */
156119async 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+
167275module . exports = { crawlTLVTrees, tlvTreePermit : tlvTreePermit } ;
0 commit comments