Blog posts are a key component in learning new technologies accross the web. With OpenAI’s GPT , we can
take this a step further by summarizing blog posts into a few sentences. Allowing quicker consumption while retaining
key concepts.
In this article, we will learn how to scrape blog post content and summarize it using OpenAI’s GPT via TypeScript.
Before getting started, please ensure you have an OpenAI account. If not, you can signup on their website
here . Once signed up, take note of your API Key as we’ll need it later.
This project will be using Node.js 18.x. You can check your Node.js version by running node -v
within the terminal.
Let’s begin by initializing our project and installing dependencies:
# Create the project directory and navigating into it
mkdir summarize-blog-posts-with-typescript-and-openais-gpt && cd $_
# Initialize project
npm init -y && npm pkg set type=module
# Install dependencies
npm i openai gpt-3-encoder cheerio user-agents dotenv typescript @types/node @types/user-agents
copied = true);
$el.setAttribute('data-checked', 'true');
$el.firstElementChild.classList.add('starting:scale-0', 'starting:opacity-0');
setTimeout(() => $el.removeAttribute('data-checked'), 2500);
" @mouseleave.debounce.1000ms="copied && (copied = false)" @keydown.enter.debounce.1000ms="copied && (copied = false)" @keydown.space.debounce.1000ms="copied && (copied = false)" @touchstart.debounce.1000ms="copied && (copied = false)" class="group flex items-center justify-between gap-2 rounded p-2 select-none hover:bg-surface-3 hover:text-brand focus-visible:ring-ring focus-visible:ring-1 focus-visible:outline-none motion-safe:transition-colors text-foreground-2 absolute top-0 right-0">
Now that we have our project setup, let’s create an .env
file and place our secret OpenAI API Key in there.
.env OPENAI_API_KEY = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
copied = true);
$el.setAttribute('data-checked', 'true');
$el.firstElementChild.classList.add('starting:scale-0', 'starting:opacity-0');
setTimeout(() => $el.removeAttribute('data-checked'), 2500);
" @mouseleave.debounce.1000ms="copied && (copied = false)" @keydown.enter.debounce.1000ms="copied && (copied = false)" @keydown.space.debounce.1000ms="copied && (copied = false)" @touchstart.debounce.1000ms="copied && (copied = false)" class="group flex items-center justify-between gap-2 rounded p-2 select-none hover:bg-surface-3 hover:text-brand focus-visible:ring-ring focus-visible:ring-1 focus-visible:outline-none motion-safe:transition-colors text-foreground-2 absolute top-0 right-0">
Next, create a tsconfig.json
file at the root of our project. This will allow us to leverage TypeScript within our
project.
You can read about each of these options in more detail here .
tsconfig.json {
"compilerOptions" : {
"target" : "es2022" ,
"module" : "es2022" ,
"esModuleInterop" : true ,
"forceConsistentCasingInFileNames" : true ,
"strict" : true ,
"skipLibCheck" : true ,
"moduleResolution" : "node" ,
"noEmit" : true ,
"allowImportingTsExtensions" : true
} ,
"ts-node" : { "esm" : true } ,
"include" : [ "**/*.ts" ] ,
"exclude" : [ "node_modules" ]
}
copied = true);
$el.setAttribute('data-checked', 'true');
$el.firstElementChild.classList.add('starting:scale-0', 'starting:opacity-0');
setTimeout(() => $el.removeAttribute('data-checked'), 2500);
" @mouseleave.debounce.1000ms="copied && (copied = false)" @keydown.enter.debounce.1000ms="copied && (copied = false)" @keydown.space.debounce.1000ms="copied && (copied = false)" @touchstart.debounce.1000ms="copied && (copied = false)" class="group flex items-center justify-between gap-2 rounded p-2 select-none hover:bg-surface-3 hover:text-brand focus-visible:ring-ring focus-visible:ring-1 focus-visible:outline-none motion-safe:transition-colors text-foreground-2 absolute top-0 right-0">
Lastly, create an index.ts
file at the root of our project. Within this file, we can load our .env
file and
initialize the OpenAI API client.
index.ts import dotenv from 'dotenv' ;
import { Configuration , OpenAIApi } from 'openai' ;
dotenv .config ();
const configuration = new Configuration ({ apiKey : process . env . OPENAI_API_KEY });
const openai = new OpenAIApi (configuration);
copied = true);
$el.setAttribute('data-checked', 'true');
$el.firstElementChild.classList.add('starting:scale-0', 'starting:opacity-0');
setTimeout(() => $el.removeAttribute('data-checked'), 2500);
" @mouseleave.debounce.1000ms="copied && (copied = false)" @keydown.enter.debounce.1000ms="copied && (copied = false)" @keydown.space.debounce.1000ms="copied && (copied = false)" @touchstart.debounce.1000ms="copied && (copied = false)" class="group flex items-center justify-between gap-2 rounded p-2 select-none hover:bg-surface-3 hover:text-brand focus-visible:ring-ring focus-visible:ring-1 focus-visible:outline-none motion-safe:transition-colors text-foreground-2 absolute top-0 right-0">
In order to summarize a blog post, we must first capture its contents. Start by using the Fetch API to get
all HTML content from the blog post’s webpage.
We must also pass a User-Agent
header to the request. Certain websites will block requests that do not have this. We
can generate one via the user-agents library.
index.ts import dotenv from 'dotenv' ;
import { Configuration , OpenAIApi } from 'openai' ;
import UserAgent from 'user-agents' ;
dotenv .config ();
const configuration = new Configuration ({ apiKey : process . env . OPENAI_API_KEY });
const openai = new OpenAIApi (configuration);
const headers = { 'User-Agent' : new UserAgent ( /Chrome/ ) .toString () };
const url = 'https://openai.com/blog/introducing-chatgpt-and-whisper-apis' ;
const html = await fetch (url , { headers }) .then ((res) => res .text ());
copied = true);
$el.setAttribute('data-checked', 'true');
$el.firstElementChild.classList.add('starting:scale-0', 'starting:opacity-0');
setTimeout(() => $el.removeAttribute('data-checked'), 2500);
" @mouseleave.debounce.1000ms="copied && (copied = false)" @keydown.enter.debounce.1000ms="copied && (copied = false)" @keydown.space.debounce.1000ms="copied && (copied = false)" @touchstart.debounce.1000ms="copied && (copied = false)" class="group flex items-center justify-between gap-2 rounded p-2 select-none hover:bg-surface-3 hover:text-brand focus-visible:ring-ring focus-visible:ring-1 focus-visible:outline-none motion-safe:transition-colors text-foreground-2 absolute top-0 right-0">
Using Cheerio , the blog post content must be extracted from the the HTML. This is essential as we only want
to summarize the blog post content and not the entire page.
Web scraping can be tricky as each website is built different. You may need to adjust the selectors below to fit your
needs.
Also, OpenAI’s GPT has a max number of tokens that can be passed into it. This means we must truncate
our content to fit within this limit. Thankfully, we can leverage gpt-3-encoder to do all the heavy
lifting.
For this example, we will be capturing the first 8000 tokens. The max token count will vary based on the GPT model
your using. I recommend using GPT-4 as it has a much higher limit.
index.ts import { load } from 'cheerio' ;
import dotenv from 'dotenv' ;
import { decode , encode } from 'gpt-3-encoder' ;
import { Configuration , OpenAIApi } from 'openai' ;
import UserAgent from 'user-agents' ;
dotenv .config ();
const configuration = new Configuration ({ apiKey : process . env . OPENAI_API_KEY });
const openai = new OpenAIApi (configuration);
const headers = { 'User-Agent' : new UserAgent ( /Chrome/ ) .toString () };
const url = 'https://openai.com/blog/introducing-chatgpt-and-whisper-apis' ;
const html = await fetch (url , { headers }) .then ((res) => res .text ());
const $ = load (html);
$ ( 'header, footer, aside, noscript' ) .remove ();
const content = $ ( 'main' ). length > 0 ? $ ( 'main p' ) : $ ( 'body p' );
const safeContent = decode ( encode ( content .text ()) .slice ( 0 , 8000 ));
copied = true);
$el.setAttribute('data-checked', 'true');
$el.firstElementChild.classList.add('starting:scale-0', 'starting:opacity-0');
setTimeout(() => $el.removeAttribute('data-checked'), 2500);
" @mouseleave.debounce.1000ms="copied && (copied = false)" @keydown.enter.debounce.1000ms="copied && (copied = false)" @keydown.space.debounce.1000ms="copied && (copied = false)" @touchstart.debounce.1000ms="copied && (copied = false)" class="group flex items-center justify-between gap-2 rounded p-2 select-none hover:bg-surface-3 hover:text-brand focus-visible:ring-ring focus-visible:ring-1 focus-visible:outline-none motion-safe:transition-colors text-foreground-2 absolute top-0 right-0">
Finally, we can pass our content to OpenAI’s GPT and receive a summary of the blog post. Keep in mind, the request may
take a few seconds to complete.
index.ts import { load } from 'cheerio' ;
import dotenv from 'dotenv' ;
import { decode , encode } from 'gpt-3-encoder' ;
import { Configuration , OpenAIApi } from 'openai' ;
import UserAgent from 'user-agents' ;
dotenv .config ();
const configuration = new Configuration ({ apiKey : process . env . OPENAI_API_KEY });
const openai = new OpenAIApi (configuration);
const headers = { 'User-Agent' : new UserAgent ( /Chrome/ ) .toString () };
const url = 'https://openai.com/blog/introducing-chatgpt-and-whisper-apis' ;
const html = await fetch (url , { headers }) .then ((res) => res .text ());
const $ = load (html);
$ ( 'header, footer, aside, noscript' ) .remove ();
const content = $ ( 'main' ). length > 0 ? $ ( 'main p' ) : $ ( 'body p' );
const safeContent = decode ( encode ( content .text ()) .slice ( 0 , 8000 ));
const chatCompletion = await openai .createChatCompletion ({
model : 'gpt-4' , // or gpt-3.5-turbo
messages : [{ role : 'user' , content : `Summarize in 1 paragraph: ${ safeContent } ` }] ,
});
const summary = chatCompletion . data .choices[ 0 ]. message ?.content;
copied = true);
$el.setAttribute('data-checked', 'true');
$el.firstElementChild.classList.add('starting:scale-0', 'starting:opacity-0');
setTimeout(() => $el.removeAttribute('data-checked'), 2500);
" @mouseleave.debounce.1000ms="copied && (copied = false)" @keydown.enter.debounce.1000ms="copied && (copied = false)" @keydown.space.debounce.1000ms="copied && (copied = false)" @touchstart.debounce.1000ms="copied && (copied = false)" class="group flex items-center justify-between gap-2 rounded p-2 select-none hover:bg-surface-3 hover:text-brand focus-visible:ring-ring focus-visible:ring-1 focus-visible:outline-none motion-safe:transition-colors text-foreground-2 absolute top-0 right-0">
Here’s an example of what the summary may look like:
---
"OpenAI now offers developers access to ChatGPT and Whisper models through its
API, enabling the integration of cutting-edge language and speech-to-text
capabilities into their apps and products. Over time, OpenAI has achieved a 90%
cost reduction for ChatGPT and will pass on these savings to API users. The
Whisper large-v2 model is also available through the API for faster and
cost-effective results. Users can expect continuous model improvements and
access to dedicated capacity for deeper control over the models. Clients like
Snap Inc., Quizlet, Instacart, Shopify, and Speak are already taking advantage
of these APIs to create AI-powered solutions. OpenAI has also made changes to
its API terms of service in response to developer feedback."
---
copied = true);
$el.setAttribute('data-checked', 'true');
$el.firstElementChild.classList.add('starting:scale-0', 'starting:opacity-0');
setTimeout(() => $el.removeAttribute('data-checked'), 2500);
" @mouseleave.debounce.1000ms="copied && (copied = false)" @keydown.enter.debounce.1000ms="copied && (copied = false)" @keydown.space.debounce.1000ms="copied && (copied = false)" @touchstart.debounce.1000ms="copied && (copied = false)" class="group flex items-center justify-between gap-2 rounded p-2 select-none hover:bg-surface-3 hover:text-brand focus-visible:ring-ring focus-visible:ring-1 focus-visible:outline-none motion-safe:transition-colors text-foreground-2 absolute top-0 right-0">
In very few lines of code, we were able to pull all content from a blog post and present a detailed summary of that
content. As mentioned earlier, this allows quick consumption while retaining key concepts.
You can see this in action on my personal project feedjoy . Under each blog post exists a summary of its
content.
summarize-blog-posts-with-typescript-and-openais-gpt
This project is a showcase of summarizing blog posts with TypeScript and OpenAI's GPT.
Introduction