10 Commits
v2.0.6 ... main

Author SHA1 Message Date
towfiqi
54522b2261 chore(release): 2.0.7 2025-02-23 22:36:38 +06:00
towfiqi
495d872bb9 chore: code styling/formatting. 2025-02-23 22:35:53 +06:00
towfiqi
07eb4bd94f chore: Updated ReadMe. 2025-02-23 22:33:42 +06:00
towfiqi
36ed4cf800 fix: Resolves AdWords integration issue. 2025-02-23 22:27:59 +06:00
towfiqi
c8601ebb84 Merge branch 'main' of https://github.com/towfiqi/serpbear 2025-02-23 22:26:55 +06:00
Towfiq I.
56d8b660c5 Merge pull request #273 from phoehnel/dev/thorw-error-on-empty-results
Add error message, if returned search HTML does not contain required elements
2025-02-23 22:23:04 +06:00
Pascal Höhnel
c34c8260c7 improve error message in UI on proxy use 2025-01-17 09:36:41 +01:00
Pascal Höhnel
cab8f518bb also add result-check to proxy 2025-01-17 09:10:43 +01:00
Pascal Höhnel
6e47a6fba7 add error message, if returned HTML does not contain required elements 2025-01-17 08:24:03 +01:00
towfiqi
bf911b4e45 fix: resolves broken CDN images. 2024-12-10 20:18:06 +06:00
8 changed files with 46 additions and 18 deletions

View File

@@ -2,6 +2,14 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [2.0.7](https://github.com/towfiqi/serpbear/compare/v2.0.6...v2.0.7) (2025-02-23)
### Bug Fixes
* Resolves AdWords integration issue. ([36ed4cf](https://github.com/towfiqi/serpbear/commit/36ed4cf800c1fd0e3df4e807faaa1fdb863df5e4))
* resolves broken CDN images. ([bf911b4](https://github.com/towfiqi/serpbear/commit/bf911b4e45b9007a05ce6399838da3276161c61d))
### [2.0.6](https://github.com/towfiqi/serpbear/compare/v2.0.5...v2.0.6) (2024-11-15)

View File

@@ -2,13 +2,13 @@
# SerpBear
![Codacy Badge](https://app.codacy.com/project/badge/Grade/7e7a0030c3f84c6fb56a3ce6273fbc1d) ![GitHub](https://img.shields.io/github/license/towfiqi/serpbear) ![GitHub package.json version](https://img.shields.io/github/package-json/v/towfiqi/serpbear) ![Docker Pulls](https://img.shields.io/docker/pulls/towfiqi/serpbear)
![Codacy Badge](https://app.codacy.com/project/badge/Grade/7e7a0030c3f84c6fb56a3ce6273fbc1d) ![GitHub](https://img.shields.io/github/license/towfiqi/serpbear) ![GitHub package.json version](https://img.shields.io/github/package-json/v/towfiqi/serpbear) ![Docker Pulls](https://img.shields.io/docker/pulls/towfiqi/serpbear) [![StandWithPalestine](https://raw.githubusercontent.com/Safouene1/support-palestine-banner/master/StandWithPalestine.svg)](https://www.youtube.com/watch?v=bjtDsd0g468&rco=1)
#### [Documentation](https://docs.serpbear.com/) | [Changelog](https://github.com/towfiqi/serpbear/blob/main/CHANGELOG.md) | [Docker Image](https://hub.docker.com/r/towfiqi/serpbear)
SerpBear is an Open Source Search Engine Position Tracking and Keyword Research App. It allows you to track your website's keyword positions in Google and get notified of their position change.
![Easy to Use Search Engine Rank Tracker](https://erevanto.sirv.com/Images/serpbear/serpbear_readme_v2.gif)
![Easy to Use Search Engine Rank Tracker](https://serpbear.b-cdn.net/serpbear_readme_v2.gif)
#### Features

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "serpbear",
"version": "2.0.6",
"version": "2.0.7",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "serpbear",
"version": "2.0.6",
"version": "2.0.7",
"dependencies": {
"@googleapis/searchconsole": "^1.0.5",
"@isaacs/ttlcache": "^1.4.1",

View File

@@ -1,6 +1,6 @@
{
"name": "serpbear",
"version": "2.0.6",
"version": "2.0.7",
"private": true,
"scripts": {
"dev": "next dev",

View File

@@ -16,6 +16,13 @@ const proxy:ScraperSettings = {
const $ = cheerio.load(content);
let lastPosition = 0;
const hasValidContent = $('body').find('#main');
if (hasValidContent.length === 0) {
const msg = '[ERROR] Scraped search results from proxy do not adhere to expected format. Unable to parse results';
console.log(msg);
throw new Error(msg);
}
const mainContent = $('body').find('#main');
const children = $(mainContent).find('h3');

View File

@@ -163,7 +163,7 @@ export const getAdwordsKeywordIdeas = async (credentials:AdwordsCredentials, adw
}
try {
// API: https://developers.google.com/google-ads/api/rest/reference/rest/v16/customers/generateKeywordIdeas
// API: https://developers.google.com/google-ads/api/rest/reference/rest/v18/customers/generateKeywordIdeas
const customerID = account_id.replaceAll('-', '');
const geoTargetConstants = countries[country][3]; // '2840';
const reqPayload: Record<string, any> = {
@@ -178,7 +178,7 @@ export const getAdwordsKeywordIdeas = async (credentials:AdwordsCredentials, adw
reqPayload.siteSeed = { site: domain };
}
const resp = await fetch(`https://googleads.googleapis.com/v16/customers/${customerID}:generateKeywordIdeas`, {
const resp = await fetch(`https://googleads.googleapis.com/v18/customers/${customerID}:generateKeywordIdeas`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -297,7 +297,7 @@ export const getKeywordsVolume = async (keywords: KeywordType[]): Promise<{error
for (const country in keywordRequests) {
if (Object.hasOwn(keywordRequests, country) && keywordRequests[country].length > 0) {
try {
// API: https://developers.google.com/google-ads/api/rest/reference/rest/v16/customers/generateKeywordHistoricalMetrics
// API: https://developers.google.com/google-ads/api/rest/reference/rest/v18/customers/generateKeywordHistoricalMetrics
const customerID = account_id.replaceAll('-', '');
const geoTargetConstants = countries[country][3]; // '2840';
const reqKeywords = keywordRequests[country].map((kw) => kw.keyword);
@@ -306,7 +306,7 @@ export const getKeywordsVolume = async (keywords: KeywordType[]): Promise<{error
geoTargetConstants: `geoTargetConstants/${geoTargetConstants}`,
// language: `languageConstants/${language}`,
};
const resp = await fetch(`https://googleads.googleapis.com/v16/customers/${customerID}:generateKeywordHistoricalMetrics`, {
const resp = await fetch(`https://googleads.googleapis.com/v18/customers/${customerID}:generateKeywordHistoricalMetrics`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',

View File

@@ -4,10 +4,10 @@ import path from 'path';
import { getKeywordsInsight, getPagesInsight } from './insight';
import { readLocalSCData } from './searchConsole';
const serpBearLogo = 'https://erevanto.sirv.com/Images/serpbear/ikAdjQq.png';
const mobileIcon = 'https://erevanto.sirv.com/Images/serpbear/SqXD9rd.png';
const desktopIcon = 'https://erevanto.sirv.com/Images/serpbear/Dx3u0XD.png';
const googleIcon = 'https://erevanto.sirv.com/Images/serpbear/Sx3u0X9.png';
const serpBearLogo = 'https://serpbear.b-cdn.net/ikAdjQq.png';
const mobileIcon = 'https://serpbear.b-cdn.net/SqXD9rd.png';
const desktopIcon = 'https://serpbear.b-cdn.net/Dx3u0XD.png';
const googleIcon = 'https://serpbear.b-cdn.net/Sx3u0X9.png';
type SCStatsObject = {
[key:string]: {

View File

@@ -127,11 +127,15 @@ export const scrapeKeywordFromGoogle = async (keyword:KeywordType, settings:Sett
refreshedResults.error = scraperError || 'Unknown Error';
if (settings.scraper_type === 'proxy' && error && error.response && error.response.statusText) {
refreshedResults.error = `[${error.response.status}] ${error.response.statusText}`;
} else if (settings.scraper_type === 'proxy' && error) {
refreshedResults.error = error;
}
console.log('[ERROR] Scraping Keyword : ', keyword.keyword, '. Error: ', error && error.response && error.response.statusText);
console.log('[ERROR] Scraping Keyword : ', keyword.keyword);
if (!(error && error.response && error.response.statusText)) {
console.log('[ERROR_MESSAGE]: ', error);
} else {
console.log('[ERROR_MESSAGE]: ', error && error.response && error.response.statusText);
}
}
@@ -148,9 +152,17 @@ export const extractScrapedResult = (content: string, device: string): SearchRes
const extractedResult = [];
const $ = cheerio.load(content);
const hasValidContent = [...$('body').find('#search'), ...$('body').find('#rso')];
if (hasValidContent.length === 0) {
const msg = '[ERROR] Scraped search results do not adhere to expected format. Unable to parse results';
console.log(msg);
throw new Error(msg);
}
const hasNumberofResult = $('body').find('#search > div > div');
const searchResultItems = hasNumberofResult.find('h3');
let lastPosition = 0;
console.log('Scraped search results contain ', searchResultItems.length, ' desktop results.');
for (let i = 0; i < searchResultItems.length; i += 1) {
if (searchResultItems[i]) {
@@ -161,11 +173,12 @@ export const extractScrapedResult = (content: string, device: string): SearchRes
extractedResult.push({ title, url, position: lastPosition });
}
}
}
}
// Mobile Scraper
if (extractedResult.length === 0 && device === 'mobile') {
// Mobile Scraper
if (extractedResult.length === 0 && device === 'mobile') {
const items = $('body').find('#rso > div');
console.log('Scraped search results contain ', items.length, ' mobile results.');
for (let i = 0; i < items.length; i += 1) {
const item = $(items[i]);
const linkDom = item.find('a[role="presentation"]');
@@ -181,7 +194,7 @@ export const extractScrapedResult = (content: string, device: string): SearchRes
}
}
return extractedResult;
return extractedResult;
};
/**