Mapping table children content with puppeteer - javascript

My goal is to fetch .textContent from different <td> tags, each lying within a separate <tr>.
I think the problem lies within the table variable, as I am not checking the correct variable for children. Currently, data variable is only fetching the first <tr>, so price evaluates with this code. However, volume and turnover does not. I think it is a simple fix but I just can't figure it out!
JavaScript:
try {
const tradingData = await page.evaluate(() => {
let table = document.querySelector("#trading-data tbody");
let tableData = Array.from(table.children);
let data = tableData.map(tradeData => {
console.log(tradeData);
let price = tradeData.querySelector(".quoteapi-price").textContent;
console.log(price);
let volume = tradeData.querySelector("quoteapi-volume").textContent;
console.log(volume);
let turnover = tradeData.querySelector("quoteapi-value").textContent;
console.log(turnover);
return { price, volume, turnover };
})
return data;
});
console.log(tradingData);
} catch (err) {
console.log(err);
}
HTML:
<table id="trading-data" class="qq_table">
<tbody>
<tr class="qq_tr_border_bot">
<td>Price</td>
<td class="qq_td_right quoteapi-number quoteapi-price" data-quoteapi="price">$0.105</td>
</tr>
<tr class="qq_tr_border_bot">
<td>Change</td>
<td class="qq_td_right pos" data-quoteapi="changeSignCSS">
<span data-quoteapi="change (signed)" class="quoteapi-number quoteapi-price quoteapi-change">0.005</span>
<span data-quoteapi="pctChange (pct)" class="quoteapi-number quoteapi-pct-change">(5.00%)</span>
</td>
</tr>
<tr class="qq_tr_border_bot">
<td>Volume</td>
<td class="qq_td_right quoteapi-number quoteapi-volume" data-quoteapi="volume scale=false">5,119,162</td>
</tr>
<tr>
<td>Turnover</td>
<td class="qq_td_right quoteapi-number quoteapi-value" data-quoteapi="value scale=false">$540,173</td>
</tr>
</tbody>
</table>
For example, this should return price="$0.11", volume="3,900,558", turnover="$412,187"

You only need the map function when you are expecting multiple tables or tbodies. As this seems not to be the case in your example, you can do it like this:
const tradingData = await page.evaluate(() => {
let table = document.querySelector("#trading-data tbody");
let price = table.querySelector(".quoteapi-price").textContent;
let volume = table.querySelector(".quoteapi-volume").textContent;
let turnover = table.querySelector(".quoteapi-value").textContent;
return { price, volume, turnover };
});
console.log(tradingData);

Related

API_Multiple users

**I need to make a table with 6 random users using html. I only get one user data !
what should i do ? do i have to make a loop ?
this is the code i useed **
i tried to change the id but there was no output.
please let me know what approach i shall follow.
`
<body>
<h2>API</h2>
<table>
<thead>
<th>Full Name</th>
<th>Age</th>
<th>Gender</th>
<th>Location</th>
<th>Country</th>
</thead>
<tbody>
<tr>
<td id="fullname"></td>
<td id="age"></td>
<td id="gender"></td>
<td id="location"></td>
<td id="counrty"></td>
</tr>
<tr>
<td id="fullname"></td>
<td id="age"></td>
<td id="gender"></td>
<td id="location"></td>
<td id="counrty"></td>
</tr>
</table>
<script>
const api_url="https://randomuser.me/api/";
async function getUser() {
const response= await fetch(api_url);
const data= await response.json();
const user=data.results[0];
let{first, last} = user.name;
let{gender, email, phone} = user;
let age = user.dob.age;
let{city, state, country} = user.location;
let fullName = first + " " + last;
document.querySelector("#fullname").textContent = fullName;
document.querySelector("#age").textContent = age;
document.querySelector("#gender").textContent = gender;
document.querySelector("#location").textContent = city + " ," + state;
document.querySelector("#counrty").textContent= country;
}
getUser();
</script>
</body>
</html>
`
what shall i do to take more random users ?
shall i create more ids?
I advise you when using any other people's code (api/framework etc.) to go and look at the documentation.
As you can see from my code below, you can specify how many users you want as return from the api and do a simple forEach to insert them into the table.
const userNumber = 6;
const api_url = "https://randomuser.me/api/?results=" + userNumber; //change number base to your need.
async function getUser() {
const response = await fetch(api_url);
const data = await response.json();
data.results.forEach(user => {
let {
first,
last
} = user.name;
let {
gender,
email,
phone
} = user;
let age = user.dob.age;
let {
city,
state,
country
} = user.location;
let fullName = first + " " + last;
document.querySelector('tbody').innerHTML +=
`<tr>
<td data-fullname>${fullName}</td>
<td data-age>${age}</td>
<td data-gender>${gender}</td>
<td data-location>${city} , ${state}</td>
<td data-country>${country}</td>
</tr>`;
});
}
getUser();
<h2>API</h2>
<table border="1">
<thead>
<th>Full Name</th>
<th>Age</th>
<th>Gender</th>
<th>Location</th>
<th>Country</th>
</thead>
<tbody></tbody>
</table>
Remember instead that the ID of the elements must be unique

Updating the DOM with arrays JavaScript

I am having a problem when I try to update the DOM with new information coming from an API.
Every time that I click to add new users, the array displays the old, and new information. Ideally, it would update the array first and then display only the new information. I will attach a picture of what is happening. I would like to every time the user click on add new user, the DOM update with only the information of that new user.
HTML part
<table class="table is-fullwidth table is-hoverable table-info">
<thead>
<tr">
<th title="Channel Name" class="has-text-left"> Channel Name </th>
<th title="View per week" class="has-text-right"> View per week </th>
</tr>
</thead>
<tbody id="body-table">
<tr id="tr-table">
</tr>
</tbody>
</table>
script.js
const trline = document.getElementById('body-table')
let usersList = [];
async function getnewUsers(){
const res = await fetch('https://randomuser.me/api')
const data = await res.json()
// create an instance of the results
const user = data.results[0]
// create the new user
const newUser = {
name:`${user.name.first} ${user.name.last}`,
social: Math.floor(Math.random() * 10000 )
}
// update the new user to the database...
addData(newUser)
}
function addData(obj) {
usersList.push(obj)
// update the information on the screen
updateDOM()
}
function updateDOM( providedData = usersList){
providedData.forEach(item => {
const element = document.createElement('tr')
element.innerHTML = `
<td class="has-text-left cname"> ${item.name} </td>
<td class="has-text-right cview"> ${item.social} k</td>
`
trline.appendChild(element)
})
}
addUser.addEventListener('click', getnewUsers)
Result picture:
I found the problem and the solution.
I didn't reset the HTML part to clear before adding a new item. I had to fix the function updateDOM with this: trline.innerHTML = ''
After that, the function works fine.
function updateDOM( providedData = usersList){
trline.innerHTML = '' // clear everything before adding new stuff
providedData.forEach(item => {
const element = document.createElement('tr')
element.innerHTML = `
<td class="has-text-left cname"> ${item.name} </td>
<td class="has-text-right cview"> ${item.social} k</td>
`
trline.appendChild(element)
})
}

Puppeteer `textContent of undefined`

I'm trying to scrape some stats from basketball-reference using Puppeteer, but having some troubles accessing textContent of each of the <tr> in the table. The DOM structure looks something like
<table id="per_game_stats">
<tbody>
<tr>
<td data-stat="g">22</td>
</tr>
<tr>
<td data-stat="g">23</td>
</tr>
<tr>
<td data-stat="g">24</td>
</tr>
</tbody>
</table>
Here's my Puppeteer code that tries to access those values
const statsPerGame = await page.evaluate(() => {
const rows = Array.from(document.querySelectorAll('table#per_game_stats > tbody > tr'))
return rows.map((element) => element.innerHTML)
})
console.log(statsPerGame) // ... '<td data-stat="g">22</td>'
Okay, so element definitely exists. I want to get the value using textContent, but an error is thrown
const statsPerGame = await page.evaluate(() => {
const rows = Array.from(document.querySelectorAll('table#per_game_stats > tbody > tr'))
return rows.map((element) => element.querySelector('td[data-stat="g"]').textContent)
})
// TypeError: Cannot read property 'textContent' of null
Can someone help me out?

How i can clear/refresh my table before getting new data from api?

I am trying to retrieve datas from an api by triggering a button.but evertimes i click the button the old datas remain exist which i dont want.i want the table will be reloaded and will have new datas from api.
const showData = document.querySelector('.showData')
const btn = document.querySelector('.shwData-btn')
btn.addEventListener('click', showdata)
function showdata(){
fetch('http://localhost:5000/posts')
.then(res => res.json())
.then(data=>{
data.forEach(item =>{
const id = item['_id']
const name = item.name
const email = item.email
const age = item.age
const tr = document.createElement('tr')
tr.innerHTML = `
<tr>
<td>${id}</td>
<td>${name}</td>
<td>${email}</td>
<td>${age}</td>
</tr>
`
showData.appendChild(tr)
})})}
<!-- language: lang-html -->
<button class="shwData-btn">Showdata</button>
<table class="showData">
<tr>
<td>id</td>
<td>email</td>
<td>name</td>
<td>age</td>
</tr>
</table>
You will have to render a blank table or clear all rows(tr) before populating it with data.
const showData = document.querySelector('.showData')
const btn = document.querySelector('.shwData-btn')
btn.addEventListener('click', showdata)
function showdata(){
fetch('http://localhost:5000/posts')
.then(res => res.json())
.then(data=>{
// Clear your table here or populate with blank data
// tbody because you do not want to clear column heading. Make sure you have tbody and theader
$(".showData tbody tr").remove();
data.forEach(item =>{
const id = item['_id']
const name = item.name
const email = item.email
const age = item.age
const tr = document.createElement('tr')
tr.innerHTML = `
<tr>
<td>${id}</td>
<td>${name}</td>
<td>${email}</td>
<td>${age}</td>
</tr>
`
showData.appendChild(tr)
})})}
<!-- language: lang-html -->
<button class="shwData-btn">Showdata</button>
<table class="showData">
<tr>
<td>id</td>
<td>email</td>
<td>name</td>
<td>age</td>
</tr>
</table>
Highly recommend to have a look at this as well:
Delete all rows in an HTML table

Verify table Row's data attribute with keys in a dictionary

What I'm trying to do here, is, for every row in the table, I want to verify the row's data attribute (Note that the data attribute of this row is the socket One of the keys in the clientel dictionary previously made) with the keys in the dictionary clientel. If both match, do nothing. If the key is in the row but not in the dictionary, perform a function and if there's a key in the dictionary but it's not in a row, then add that row.
let clientel = {
socket101: ['Rick', '192.590.49.1', 'Win10', 'Norway', '15:49.00'],
socket102: ['Anthony', '192.90.897.0', 'Win7', 'Negritine', '19:19:38']
};
function man_table() {
const table = document.getElementById('table-body');
for(let i in clientel) {
for(let ih = 0, row; row = table.rows[ih]; ih++) {
ass = row.getAttribute('data');
if (ass in clientel) {}
else if (!(ass in clientel)) {table.deleteRow(ih); continue;}
else if (clientel[i] !== ass) {
let row = table.insertRow(i);
let client = clientel[i];
row.setAttribute('data', i);
let name = row.insertCell(0);
let ip = row.insertCell(1);
let os = row.insertCell(2);
let country = row.insertCell(3);
let timee = row.insertCell(4);
name.innerHTML = client[0];
ip.innerHTML = client[1];
os.innerHTML = client[2];
country.innerHTML = client[3];
timee.innerHTML = client[4];
}
}
}
}
Why doesn't this add the tables and
Is there a better way to do this?
Example of the HTML table (On Request):
<div id="table">
<table>
<thead>
<tr>
<th>Name</th>
<th>IP</th>
<th>OS</th>
<th>Country</th>
<th>Connected Since</th>
</tr>
</thead>
<tbody id="table-body">
<tr>
<td>Rick</td>
<td>192.423.41.5</td>
<td>Win 7</td>
<td>Bulgaria</td>
<td>A few moments</td>
</tr>
</tbody>
</table>
</div>
Don't try and do too many things at the same time. Adding rows in the same loop where you're deleting rows is going to cause confusion.
Notice that your HTML does not actually have the data attributes on the TR elements, so your code will never match any rows. Also, trying to this: let row = table.insertRow(i); will fail because i is a string ("socket101" etc)
The delete first looks for rows that don't have a corresponding entry in the clientel dictionary. [...table.rows] converts the HTMLCollection into an array so that filter can be used, which simply returns the entry from the dictionary matching it's data attribute. This will be null for any row that doesn't have an entry.
Once we have a list of rows that don't have matching clients remove the rows. Find the index of the row by deconstructing the row ( .forEach({rowIndex}) => foo(rowIndex) has the same effect as .forEach(row) => foo(row.rowIndex) ), and then delete the row (remembering to account for the table header row).
Adding the new row is about the same as the delete. The .map( (key, index) ) => [ key, index ] ) is used to preserve the index of each client so the row can be added in the correct place later. The filter is similar as the delete but instead of including things that exist, it includes anything that doesn't exist. This depends on null being effectively the same as false (i.e !null evaluates as true). Adding rows is done by using HTML, which is faster than creating elements/nodes individually.
let clientel = {
socket101: ['Rick', '192.590.49.1', 'Win10', 'Norway', '15:49.00'],
socket102: ['Anthony', '192.90.897.0', 'Win7', 'Negritine', '19:19:38']
};
function init() {
const table = document.getElementById('table-body');
// remove anything that doesn't have a row in the clientel map
[...table.rows].filter( (row) => !clientel[row.getAttribute('data')] )
.forEach( ({rowIndex}) => table.deleteRow(rowIndex - 1) )
// add anything that doesn't exist in the table
Object.keys(clientel)
.map( (key, index) => [ key, index ] )
.filter( ([key, index]) => !table.querySelector(`tr[data="${key}"]`) )
.forEach( ([key, index]) => {
var row = table.insertRow(index)
row.setAttribute('data', key);
row.innerHTML = clientel[key].map( value => `<td>${value}</td>` ).join("");
});
}
document.addEventListener("DOMContentLoaded", init);
</script>
<div id="table">
<table>
<thead>
<tr>
<th>Name</th>
<th>IP</th>
<th>OS</th>
<th>Country</th>
<th>Connected Since</th>
</tr>
</thead>
<tbody id="table-body">
<tr data="socket101">
<td>Rick</td>
<td>192.423.41.5</td>
<td>Win 7</td>
<td>Bulgaria</td>
<td>A few moments</td>
</tr>
<tr data="socket103">
<td>Whoever</td>
<td>127.0.0.1</td>
<td>OS/1</td>
<td>The Moon</td>
<td>Whatever</td>
</tr>
</tbody>
</table>
</div>
</body>

Categories