<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Web Scraping Archives - Be on the Right Side of Change</title>
	<atom:link href="https://blog.finxter.com/category/web-scraping/feed/" rel="self" type="application/rss+xml" />
	<link>https://blog.finxter.com/category/web-scraping/</link>
	<description></description>
	<lastBuildDate>Mon, 29 Dec 2025 11:36:39 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0.4</generator>

<image>
	<url>https://blog.finxter.com/wp-content/uploads/2020/08/cropped-cropped-finxter_nobackground-32x32.png</url>
	<title>Web Scraping Archives - Be on the Right Side of Change</title>
	<link>https://blog.finxter.com/category/web-scraping/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>The JSON Trick &#8211; How to Scrape All Answers in a Subreddit? (Example Find User Needs)</title>
		<link>https://blog.finxter.com/the-json-trick-how-to-scrape-all-answers-in-a-subreddit-example-find-user-needs/</link>
		
		<dc:creator><![CDATA[Chris]]></dc:creator>
		<pubDate>Mon, 29 Dec 2025 11:17:42 +0000</pubDate>
				<category><![CDATA[Data Science]]></category>
		<category><![CDATA[Web Scraping]]></category>
		<guid isPermaLink="false">https://blog.finxter.com/?p=1671633</guid>

					<description><![CDATA[<p>Step 1: Open a subreddit with lots of comments and answers relevant to your niche. Step 2: Append &#8220;.json&#8221; to the URL Step 3: Copy and paste the whole JSON data into ChatGPT/Gemini/Claude Step 4: Prompt something like this to discover user needs: Here&#8217;s a sample output on my ChatGPT 5.2 Thinking: It also shows ... <a title="The JSON Trick &#8211; How to Scrape All Answers in a Subreddit? (Example Find User Needs)" class="read-more" href="https://blog.finxter.com/the-json-trick-how-to-scrape-all-answers-in-a-subreddit-example-find-user-needs/" aria-label="Read more about The JSON Trick &#8211; How to Scrape All Answers in a Subreddit? (Example Find User Needs)">Read more</a></p>
<p>The post <a href="https://blog.finxter.com/the-json-trick-how-to-scrape-all-answers-in-a-subreddit-example-find-user-needs/">The JSON Trick &#8211; How to Scrape All Answers in a Subreddit? (Example Find User Needs)</a> appeared first on <a href="https://blog.finxter.com">Be on the Right Side of Change</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><strong>Step 1</strong>: Open a subreddit with lots of comments and answers relevant to your niche.</p>



<p class="wp-block-paragraph"><strong>Step 2</strong>: Append &#8220;.json&#8221; to the URL</p>



<ul class="wp-block-list">
<li>Example Subreddit URL: <a href="https://www.reddit.com/r/GeminiAI/comments/1pyi6ax/how_to_tell_if_an_image_is_ai_generated" target="_blank" rel="noreferrer noopener">https://www.reddit.com/r/GeminiAI/comments/1pyi6ax/how_to_tell_if_an_image_is_ai_generated</a></li>



<li>Example JSON: <a href="https://www.reddit.com/r/GeminiAI/comments/1pyi6ax/how_to_tell_if_an_image_is_ai_generated.json" target="_blank" rel="noreferrer noopener">https://www.reddit.com/r/GeminiAI/comments/1pyi6ax/how_to_tell_if_an_image_is_ai_generated<strong>.json</strong></a></li>
</ul>



<p class="wp-block-paragraph"><strong>Step 3</strong>: Copy and paste the whole JSON data into ChatGPT/Gemini/Claude</p>



<p class="wp-block-paragraph"><strong>Step 4</strong>: Prompt something like this to discover user needs:</p>



<pre class="wp-block-code"><code>Look at this Subreddit (JSON format). Find and list all user pain points, needs, and struggles. Suggest top products and unique selling propositions (USPs) to satisfy these needs. Research similar products and find unique positions.</code></pre>



<p class="wp-block-paragraph">Here&#8217;s a sample output on my ChatGPT 5.2 Thinking:</p>



<figure class="wp-block-image size-full"><img fetchpriority="high" decoding="async" width="832" height="1022" src="https://blog.finxter.com/wp-content/uploads/2025/12/image-49.png" alt="" class="wp-image-1671636" srcset="https://blog.finxter.com/wp-content/uploads/2025/12/image-49.png 832w, https://blog.finxter.com/wp-content/uploads/2025/12/image-49-244x300.png 244w, https://blog.finxter.com/wp-content/uploads/2025/12/image-49-768x943.png 768w" sizes="(max-width: 832px) 100vw, 832px" /></figure>



<p class="wp-block-paragraph">It also shows a number of products that would fill these needs:</p>



<figure class="wp-block-image size-large"><img decoding="async" width="811" height="1024" src="https://blog.finxter.com/wp-content/uploads/2025/12/image-50-811x1024.png" alt="" class="wp-image-1671637" srcset="https://blog.finxter.com/wp-content/uploads/2025/12/image-50-811x1024.png 811w, https://blog.finxter.com/wp-content/uploads/2025/12/image-50-238x300.png 238w, https://blog.finxter.com/wp-content/uploads/2025/12/image-50-768x969.png 768w, https://blog.finxter.com/wp-content/uploads/2025/12/image-50.png 855w" sizes="(max-width: 811px) 100vw, 811px" /></figure>



<p class="wp-block-paragraph">I omit the rest for brevity &#8211; you get the point. </p>



<p class="wp-block-paragraph">This truly is a goldmine of business need analysis.</p>
<p>The post <a href="https://blog.finxter.com/the-json-trick-how-to-scrape-all-answers-in-a-subreddit-example-find-user-needs/">The JSON Trick &#8211; How to Scrape All Answers in a Subreddit? (Example Find User Needs)</a> appeared first on <a href="https://blog.finxter.com">Be on the Right Side of Change</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Sо уӧυ&#8217;гё sтϊll υsᎥng r̸e̴g͟e̸x̷ to parse H̶T̷M̵L</title>
		<link>https://blog.finxter.com/so-youre-using-regex-to-parse-html/</link>
		
		<dc:creator><![CDATA[Chris]]></dc:creator>
		<pubDate>Thu, 02 May 2024 10:41:56 +0000</pubDate>
				<category><![CDATA[HTML]]></category>
		<category><![CDATA[Regex]]></category>
		<category><![CDATA[Text Processing]]></category>
		<category><![CDATA[Web Scraping]]></category>
		<guid isPermaLink="false">https://blog.finxter.com/?p=1670234</guid>

					<description><![CDATA[<p>🤪 About: This article does only one thing: giving this excellent StackOverflow answer a platform &#8212; and formatting it a bit for ease of reading. You can&#8217;t parse [X]HTML with regex. Because HTML can&#8217;t be parsed by regex. Regex is not a tool that can be used to correctly parse HTML. As I have answered ... <a title="Sо уӧυ&#8217;гё sтϊll υsᎥng r̸e̴g͟e̸x̷ to parse H̶T̷M̵L" class="read-more" href="https://blog.finxter.com/so-youre-using-regex-to-parse-html/" aria-label="Read more about Sо уӧυ&#8217;гё sтϊll υsᎥng r̸e̴g͟e̸x̷ to parse H̶T̷M̵L">Read more</a></p>
<p>The post <a href="https://blog.finxter.com/so-youre-using-regex-to-parse-html/">Sо уӧυ&#8217;гё sтϊll υsᎥng r̸e̴g͟e̸x̷ to parse H̶T̷M̵L</a> appeared first on <a href="https://blog.finxter.com">Be on the Right Side of Change</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="has-base-2-background-color has-background wp-block-paragraph"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f92a.png" alt="🤪" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>About</strong>: This article does only one thing: giving <a href="https://stackoverflow.com/a/1732454">this excellent StackOverflow answer</a> a platform &#8212; and formatting it a bit for ease of reading.</p>



<p class="wp-block-paragraph">You can&#8217;t parse [X]HTML with regex. Because HTML can&#8217;t be parsed by regex. Regex is not a tool that can be used to correctly parse HTML. </p>



<p class="wp-block-paragraph">As I have answered in HTML-and-regex questions here so many times before, the use of regex will not allow you to consume HTML. </p>



<p class="wp-block-paragraph">Regular expressions are a tool that is insufficiently sophisticated to understand the constructs employed by HTML. HTML is not a regular language and hence cannot be parsed by regular expressions. </p>



<p class="wp-block-paragraph">Regex queries are not equipped to break down HTML into its meaningful parts. </p>



<p class="wp-block-paragraph">so many times but it is not getting to me. Even enhanced irregular regular expressions as used by Perl are not up to the task of parsing HTML. You will never make me crack. </p>



<p class="wp-block-paragraph">HTML is a language of sufficient complexity that it cannot be parsed by regular expressions. </p>



<p class="wp-block-paragraph">Even Jon Skeet cannot parse HTML using regular expressions. Every time you attempt to parse HTML with regular expressions, the unholy child weeps the blood of virgins, and Russian hackers pwn your webapp. </p>



<p class="wp-block-paragraph">Parsing HTML with regex summons tainted souls into the realm of the living. HTML and regex go together like love, marriage, and ritual infanticide. </p>



<p class="wp-block-paragraph">The &lt;center> cannot hold it is too late. </p>



<p class="wp-block-paragraph">The force of regex and HTML together in the same conceptual space will destroy your mind like so much watery putty. If you parse HTML with regex you are giving in to Them and their blasphemous ways which doom us all to inhuman toil for the One whose Name cannot be expressed in the Basic Multilingual Plane, he comes. HTML-plus-regexp will liquify the n​erves of the sentient whilst you observe, your psyche withering in the onslaught of horror. </p>



<p class="wp-block-paragraph">Rege̿̔̉x-based HTML parsers are the cancer that is killing StackOverflow <em>it is too late it is too late we cannot be saved</em> the transgression of a chi͡ld ensures regex will consume all living tissue (except for HTML which it cannot, as previously prophesied) <em>dear lord help us how can anyone survive this scourge</em> using regex to parse HTML has doomed humanity to an eternity of dread torture and security holes <em>using rege</em>x as a tool to process HTML establishes a brea<em>ch between this world</em> and the dread realm of c͒ͪo͛ͫrrupt entities (like SGML entities, but <em>more corrupt) a mere glimp</em>se of the world of reg​<strong>ex parsers for HTML will ins</strong>​tantly transport a p<em>rogrammer&#8217;s consciousness i</em>nto a w<em>orl</em>d of ceaseless screaming, he comes, the pestilent slithy regex-infection wil​<strong>l devour your HT</strong>​ML parser, application and existence for all time like Visual Basic only worse <em>he comes he com</em>es <em>do not fi</em>​ght h<strong>e com̡e̶s, ̕h̵i</strong>​s un̨ho͞ly radiańcé de<em>stro҉ying all enli̍̈́̂̈́ghtenment, HTML tags <strong>lea͠ki̧n͘g fr̶ǫm ̡yo​͟ur eye͢s̸ ̛l̕ik͏e liq</strong>​uid p</em>ain, the song of re̸gular exp​ression parsing will exti<em>​nguish the voices of mor​<strong>tal man from the sp</strong>​here I can see it can you see ̲͚̖͔̙î̩́t̲͎̩̱͔́̋̀ it is beautiful t​</em>he f<code>inal snuf</code>fing o<em>f the lie​<strong>s of Man ALL IS LOŚ͖̩͇̗̪̏̈́T A</strong></em><strong>LL I​S L</strong>OST th<em>e pon̷y he come</em>s he c̶̮omes he co<strong>mes t<em>he</em> ich​</strong>or permeat<em>es al</em>l MY FAC<em>E MY FACE ᵒh god n<strong>o NO NOO̼</strong></em><strong>O​O N</strong>Θ stop t<em>he an​*̶͑̾̾​̅ͫ͏̙̤g͇̫͛͆̾ͫ̑͆l͖͉̗̩̳̟̍ͫͥͨ</em>e̠̅s<code> ͎a̧͈͖r̽̾̈́͒͑e</code> n<strong>​ot rè̑ͧ̌aͨl̘̝̙̃ͤ͂̾̆ ZA̡͊͠͝LGΌ ISͮ̂҉̯͈͕̹̘̱ T</strong>O͇̹̺ͅƝ̴ȳ̳ TH̘<strong>Ë͖́̉ ͠P̯͍̭O̚​N̐Y̡ H̸̡̪̯ͨ͊̽̅̾̎Ȩ̬̩̾͛ͪ̈́̀́͘ ̶̧̨̱̹̭̯ͧ̾ͬC̷̙̲̝͖ͭ̏ͥͮ͟Oͮ͏̮̪̝͍M̲̖͊̒ͪͩͬ̚̚͜Ȇ̴̟̟͙̞ͩ͌͝</strong>S̨̥̫͎̭ͯ̿̔̀ͅ</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Source</strong>: <a href="https://stackoverflow.com/a/1732454">https://stackoverflow.com/a/1732454</a></p>
<p>The post <a href="https://blog.finxter.com/so-youre-using-regex-to-parse-html/">Sо уӧυ&#8217;гё sтϊll υsᎥng r̸e̴g͟e̸x̷ to parse H̶T̷M̵L</a> appeared first on <a href="https://blog.finxter.com">Be on the Right Side of Change</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Creating a Live Bitcoin Price Chart Using Binance API (No API Key!)</title>
		<link>https://blog.finxter.com/creating-a-live-bitcoin-price-chart-using-binance-api-no-api-key/</link>
		
		<dc:creator><![CDATA[Jean Rousseau]]></dc:creator>
		<pubDate>Sun, 28 Apr 2024 14:14:46 +0000</pubDate>
				<category><![CDATA[App Development]]></category>
		<category><![CDATA[Bitcoin]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Web Development]]></category>
		<category><![CDATA[Web Scraping]]></category>
		<guid isPermaLink="false">https://blog.finxter.com/?p=1670164</guid>

					<description><![CDATA[<p>In this tutorial, we&#8217;ll learn how to create a simple live-updating Bitcoin price chart using the Binance API. This straightforward guide will cover all the necessary HTML, JavaScript, and CSS code required to get started with real-time cryptocurrency price tracking. You don&#8217;t need much for this tutorial: ✅ We&#8217;ll create a single HTML document that ... <a title="Creating a Live Bitcoin Price Chart Using Binance API (No API Key!)" class="read-more" href="https://blog.finxter.com/creating-a-live-bitcoin-price-chart-using-binance-api-no-api-key/" aria-label="Read more about Creating a Live Bitcoin Price Chart Using Binance API (No API Key!)">Read more</a></p>
<p>The post <a href="https://blog.finxter.com/creating-a-live-bitcoin-price-chart-using-binance-api-no-api-key/">Creating a Live Bitcoin Price Chart Using Binance API (No API Key!)</a> appeared first on <a href="https://blog.finxter.com">Be on the Right Side of Change</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">In this tutorial, we&#8217;ll learn how to create a simple live-updating Bitcoin price chart using the Binance API. This straightforward guide will cover all the necessary HTML, JavaScript, and CSS code required to get started with real-time cryptocurrency price tracking.</p>



<p class="wp-block-paragraph">You don&#8217;t need much for this tutorial: <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> </p>



<ol class="wp-block-list">
<li>Basic understanding of HTML, CSS, and JavaScript.</li>



<li>A modern web browser.</li>
</ol>



<p class="wp-block-paragraph">We&#8217;ll create a single HTML document that includes inline JavaScript and CSS. This approach ensures that you can easily copy, paste, and run the example without managing multiple files.</p>



<p class="has-global-color-8-background-color has-background wp-block-paragraph"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4c8.png" alt="📈" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Execution</strong>: To run the Bitcoin price checker, copy the entire HTML code into a file with a <code>.html</code> extension. Open this file in any modern web browser to see the live Bitcoin price updating every few seconds.</p>



<p class="wp-block-paragraph">You can see it live right here:</p>



<!DOCTYPE html>
<html lang="en">
<head>
    
    
    <title>Live Bitcoin Price Chart</title>
    <style>
        body {
            font-family: Arial, sans-serif;
        }
        #chartContainer {
            height: 300px;
            width: 100%;
            margin: 20px auto;
            border: 1px solid #ccc;
        }
    </style>
</head>
<body>
    <div id="chartContainer"></div>
    <script src="https://canvasjs.com/assets/script/canvasjs.min.js"></script>
    <script>
        let priceDataPoints = [];

        function updateChartData() {
            fetch('https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT')
                .then(response => response.json())
                .then(data => {
                    const btcPrice = parseFloat(data.price);
                    const currentTime = new Date();
                    priceDataPoints.push({ x: currentTime, y: btcPrice });
                    if (priceDataPoints.length > 20) {
                        priceDataPoints.shift();
                    }
                    updateChart();
                }).catch(error => console.error("Error fetching BTC price:", error));
        }

        function updateChart() {
            let chart = new CanvasJS.Chart("chartContainer", {
                title: {
                    text: "Live Bitcoin Price"
                },
                axisX: {
                    title: "Time",
                    valueFormatString: "HH:mm:ss",
                    labelAngle: -30
                },
                axisY: {
                    title: "Price (USD)",
                    includeZero: false,
                    prefix: "$"
                },
                data: [{
                    type: "line",
                    xValueType: "dateTime",
                    dataPoints: priceDataPoints
                }]
            });
            chart.render();
        }

        updateChartData(); // Initial call to populate the chart
        setInterval(updateChartData, 5000); // Update chart every 5 seconds
    </script>
</body>
</html>



<p class="wp-block-paragraph">Here&#8217;s the full HTML document with embedded CSS and JavaScript:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;!DOCTYPE html>
&lt;html lang="en">
&lt;head>
    &lt;meta charset="UTF-8">
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0">
    &lt;title>Live Bitcoin Price Chart&lt;/title>
    &lt;style>
        body {
            font-family: Arial, sans-serif;
        }
        #chartContainer {
            height: 300px;
            width: 100%;
            margin: 20px auto;
            border: 1px solid #ccc;
        }
    &lt;/style>
&lt;/head>
&lt;body>
    &lt;div id="chartContainer">&lt;/div>
    &lt;script src="https://canvasjs.com/assets/script/canvasjs.min.js">&lt;/script>
    &lt;script>
        let priceDataPoints = [];

        function updateChartData() {
            fetch('https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT')
                .then(response => response.json())
                .then(data => {
                    const btcPrice = parseFloat(data.price);
                    const currentTime = new Date();
                    priceDataPoints.push({ x: currentTime, y: btcPrice });
                    if (priceDataPoints.length > 20) {
                        priceDataPoints.shift();
                    }
                    updateChart();
                }).catch(error => console.error("Error fetching BTC price:", error));
        }

        function updateChart() {
            let chart = new CanvasJS.Chart("chartContainer", {
                title: {
                    text: "Live Bitcoin Price"
                },
                axisX: {
                    title: "Time",
                    valueFormatString: "HH:mm:ss",
                    labelAngle: -30
                },
                axisY: {
                    title: "Price (USD)",
                    includeZero: false,
                    prefix: "$"
                },
                data: [{
                    type: "line",
                    xValueType: "dateTime",
                    dataPoints: priceDataPoints
                }]
            });
            chart.render();
        }

        updateChartData(); // Initial call to populate the chart
        setInterval(updateChartData, 5000); // Update chart every 5 seconds
    &lt;/script>
&lt;/body>
&lt;/html></pre>



<p class="wp-block-paragraph">The code works as follows:</p>



<p class="wp-block-paragraph"><strong>HTML Structure</strong>: We have a <code>div</code> element where the chart will be displayed.</p>



<p class="wp-block-paragraph"><strong>CSS Styling</strong>: Simple styles are applied to ensure the chart container is visually defined.</p>



<p class="wp-block-paragraph"><strong>JavaScript for Live Data</strong>:</p>



<ul class="wp-block-list">
<li><strong>Fetching Data</strong>: The JavaScript uses the <code>fetch</code> API to pull live Bitcoin prices from Binance&#8217;s public API endpoint.</li>



<li><strong>Updating the Chart</strong>: We use CanvasJS (a third-party library) to render the price chart. New data points are added every 5 seconds, and the oldest data points are removed to keep the chart clean and readable.</li>



<li><strong>CanvasJS Library</strong>: This is a powerful and easy-to-use chart library, which we include from a CDN. It simplifies drawing the live chart.</li>
</ul>
<p>The post <a href="https://blog.finxter.com/creating-a-live-bitcoin-price-chart-using-binance-api-no-api-key/">Creating a Live Bitcoin Price Chart Using Binance API (No API Key!)</a> appeared first on <a href="https://blog.finxter.com">Be on the Right Side of Change</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>(Solved) Python Request Error 403 When Web Scraping</title>
		<link>https://blog.finxter.com/solved-python-request-error-403-when-web-scraping/</link>
		
		<dc:creator><![CDATA[Emily Rosemary Collins]]></dc:creator>
		<pubDate>Tue, 09 Jan 2024 19:45:47 +0000</pubDate>
				<category><![CDATA[Error]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Python Requests]]></category>
		<category><![CDATA[Web Development]]></category>
		<category><![CDATA[Web Scraping]]></category>
		<guid isPermaLink="false">https://blog.finxter.com/?p=1654082</guid>

					<description><![CDATA[<p>Quick Fix Trying to parse a site when web scraping or calling APIs with Python requests, but getting slapped with a 403 Forbidden error. Ouch! The basic code looks like this: And the server is like, “Nope, you&#8217;re not coming in” 🥸, showing a 403 Forbidden error. Turns out, servers can be picky. They might ... <a title="(Solved) Python Request Error 403 When Web Scraping" class="read-more" href="https://blog.finxter.com/solved-python-request-error-403-when-web-scraping/" aria-label="Read more about (Solved) Python Request Error 403 When Web Scraping">Read more</a></p>
<p>The post <a href="https://blog.finxter.com/solved-python-request-error-403-when-web-scraping/">(Solved) Python Request Error 403 When Web Scraping</a> appeared first on <a href="https://blog.finxter.com">Be on the Right Side of Change</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">Quick Fix</h2>



<p class="wp-block-paragraph">Trying to parse a site when web scraping or calling APIs with Python <code>requests</code>, but getting slapped with a 403 Forbidden error. Ouch! The basic code looks like this:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import requests
url = 'http://example.com/'
result = requests.get(url)
print(result.content.decode())</pre>



<p class="wp-block-paragraph">And the server is like, <em>“Nope, you&#8217;re not coming in”</em> <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f978.png" alt="🥸" class="wp-smiley" style="height: 1em; max-height: 1em;" />, showing a 403 Forbidden error.</p>



<p class="wp-block-paragraph">Turns out, servers can be picky. They might block requests that don&#8217;t seem to come from a web browser. For example, Elon Musk decided to block bots from scraping Twitter content to maintain data sovereignty in the AI race. </p>



<p class="wp-block-paragraph"><strong>The trick to do it anyways?</strong> Add a <code>User-Agent</code> header to your request. It&#8217;s like wearing a disguise to look like a regular visitor.</p>



<p class="wp-block-paragraph">Here’s how you do it:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import requests
url = 'http://worldagnetwork.com/'
headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36'}
result = requests.get(url, headers=headers)
print(result.content.decode())</pre>



<p class="wp-block-paragraph">Still getting the cold shoulder with a 403 error? Time to beef up your disguise with more headers, like <code>Referer</code>. </p>



<p class="wp-block-paragraph">You can find these headers in your browser&#8217;s developer tools (<code>F12 → Network → Headers → Request Headers</code>).</p>



<p class="wp-block-paragraph">Here&#8217;s an enhanced header example:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">headers = {
    'User-Agent': 'Your User Agent',
    'Referer': 'https://example.com'
}</pre>



<p class="wp-block-paragraph"><strong>Finding User-Agent</strong>: Too lazy to dig through the Network tab? Just type <code>navigator.userAgent</code> in the Chrome developer console.</p>



<p class="wp-block-paragraph"><strong>Headers Missing in Network Tab?</strong>: Refresh the page, check any HTTP request, and scroll down to see the request headers.</p>



<h2 class="wp-block-heading">What is the 403 Forbidden Error?</h2>



<p class="wp-block-paragraph">Have you been a nasty boy (or girl)? <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f609.png" alt="😉" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p class="has-global-color-8-background-color has-background wp-block-paragraph">The 403 Forbidden Error is like a strict doorkeeper saying you can&#8217;t enter. When you try to access a webpage, your browser sends a request. If the website&#8217;s server decides you shouldn’t access that page, it responds with a <code>403 Forbidden Error</code>. It&#8217;s like the server saying, <em>“I understand what you want, but I won&#8217;t let you in.”</em> This happens for various reasons, like access control settings or website configurations.</p>



<p class="wp-block-paragraph">Here&#8217;s a handy table of common HTTP error codes and their meanings. <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f447.png" alt="👇" class="wp-smiley" style="height: 1em; max-height: 1em;" /> </p>



<figure class="wp-block-table is-style-stripes"><table><thead><tr><th><strong>Error Code</strong></th><th><strong>Meaning</strong></th></tr></thead><tbody><tr><td><strong>200 OK</strong></td><td>Everything went well. The request was successful.</td></tr><tr><td><strong>201 Created</strong></td><td>A new resource was successfully created.</td></tr><tr><td><strong>301 Moved Permanently</strong></td><td>The requested URL has been permanently moved to a new location.</td></tr><tr><td><strong>302 Found</strong></td><td>The resource temporarily resides at a different URI.</td></tr><tr><td><strong>304 Not Modified</strong></td><td>The resource hasn&#8217;t been modified since the last request.</td></tr><tr><td><strong>400 Bad Request</strong></td><td>The server couldn&#8217;t understand the request due to invalid syntax.</td></tr><tr><td><strong>401 Unauthorized</strong></td><td>Authentication is required and has failed or hasn&#8217;t been provided.</td></tr><tr><td><strong><mark>403 Forbidden</mark></strong></td><td><mark>The server understood the request but refuses to authorize it.</mark></td></tr><tr><td><strong>404 Not Found</strong></td><td>The server can&#8217;t find the requested resource.</td></tr><tr><td><strong>405 Method Not Allowed</strong></td><td>The request method is known by the server but is not supported for the resource.</td></tr><tr><td><strong>500 Internal Server Error</strong></td><td>A generic error message when the server encounters an unexpected condition.</td></tr><tr><td><strong>501 Not Implemented</strong></td><td>The server does not support the functionality required to fulfill the request.</td></tr><tr><td><strong>502 Bad Gateway</strong></td><td>The server received an invalid response from the upstream server.</td></tr><tr><td><strong>503 Service Unavailable</strong></td><td>The server is not ready to handle the request, often due to maintenance or overloading.</td></tr><tr><td><strong>504 Gateway Timeout</strong></td><td>The server didn&#8217;t receive a timely response from the upstream server.</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">These codes are part of the HTTP protocol, and they help developers understand what&#8217;s happening when their application interacts with web servers. They&#8217;re like a universal language for communicating the status of web requests.</p>



<h2 class="wp-block-heading">Solving the Python Request Forbidden Error 403 When Web Scraping</h2>



<p class="wp-block-paragraph">When you&#8217;re web scraping with Python and hit a 403 error, it&#8217;s like knocking on a door and being turned away. </p>



<p class="wp-block-paragraph">To solve this, change your approach. Use <code>headers</code> in your Python requests to mimic a real browser. </p>



<p class="wp-block-paragraph">Here&#8217;s an example:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import requests

url = 'https://example.com'
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'}
response = requests.get(url, headers=headers)</pre>



<p class="wp-block-paragraph">This code tells the server, <em>“Hey, I&#8217;m just like a regular browser, let me in!”</em></p>



<h2 class="wp-block-heading">Python Requests Post Error 403</h2>



<p class="wp-block-paragraph"><strong>Facing a 403 error when sending a POST request in Python? </strong>This is like trying to submit a form on a website and being rejected.</p>



<p class="wp-block-paragraph">To fix this, check if the website needs specific headers or cookies. Sometimes, including a &#8216;Referer&#8217; header or a valid &#8216;User-Agent&#8217; string helps:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import requests

url = 'https://example.com/post'
data = {'key': 'value'}
headers = {'User-Agent': 'Your User Agent', 'Referer': 'https://example.com'}
response = requests.post(url, data=data, headers=headers)</pre>



<p class="wp-block-paragraph">Here, you&#8217;re assuring the server that your request is legitimate.</p>



<h2 class="wp-block-heading">Python Request Get 403 Error</h2>



<p class="wp-block-paragraph">When your Python GET request returns a 403 error, it&#8217;s like being denied entry <strong>when you ask for information</strong>. To bypass this, add <code>headers</code> that make your request look like it&#8217;s coming from a regular web browser:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import requests

url = 'https://example.com'
headers = {'User-Agent': 'Your User Agent'}
response = requests.get(url, headers=headers)</pre>



<p class="wp-block-paragraph">This code is your digital disguise to get past the server&#8217;s restrictions.</p>



<h2 class="wp-block-heading">Python Requests Proxy Error 403</h2>



<p class="wp-block-paragraph">A 403 error when using a proxy in Python requests is like a bouncer blocking your disguised entry. Sometimes, servers block known proxies. Try using a different proxy or adding headers:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import requests

url = 'https://example.com'
proxies = {'http': 'http://10.10.1.10:3128'}
headers = {'User-Agent': 'Your User Agent'}
response = requests.get(url, proxies=proxies, headers=headers)</pre>



<p class="wp-block-paragraph">This approach is like changing your disguise and trying a different door.</p>



<h2 class="wp-block-heading">Python urllib.request Error 403</h2>



<p class="wp-block-paragraph">Encountering a 403 error with <code>urllib.request</code>? It&#8217;s similar to using <code><a href="https://blog.finxter.com/python-async-requests-getting-urls-concurrently-via-https/" data-type="post" data-id="1651606">requests</a></code>, but you&#8217;re using a different tool. Add a <code>User-Agent</code> in your request header:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import urllib.request

url = 'https://example.com'
req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
response = urllib.request.urlopen(req)</pre>



<p class="wp-block-paragraph">This method tells the server that your request is coming from a common web browser.</p>



<h2 class="wp-block-heading">403 Forbidden Delete Request</h2>



<p class="wp-block-paragraph">A 403 error on a DELETE request means you&#8217;re trying to remove something from the server, but it’s not allowing you. Check if you have the right permissions and if your request headers are correctly set. Sometimes, you also need an authentication token.</p>



<h2 class="wp-block-heading">Getting the Authentication Token</h2>



<p class="wp-block-paragraph">An authentication token in the Python context, or in any programming context really, is a digital key that allows you to access certain resources or services. Imagine it like a special pass that lets you into a VIP area; without it, you&#8217;re not getting in.</p>



<p class="wp-block-paragraph">When you&#8217;re coding in Python, especially when dealing with web APIs or <a href="https://blog.finxter.com/python-web-scraping-from-url-to-csv-in-no-time/" data-type="post" data-id="1313474">web scraping</a>, you often need to prove who you are to access certain data or functionalities. This is where the authentication token comes in. It&#8217;s a string (a series of characters) that verifies your identity to the server or service you&#8217;re trying to access.</p>



<p class="wp-block-paragraph">Here&#8217;s a simple breakdown:</p>



<ol class="wp-block-list">
<li><strong>Obtaining the Token</strong>: First, you need to get this token. This usually happens after you log in or send a request with your credentials (like your username and password). The server then gives you a token as a response.</li>



<li><strong>Using the Token</strong>: Once you have the token, you include it in the headers of your subsequent requests. This is like showing your pass every time you try to access something.</li>



<li><strong>Server Verification</strong>: The server checks the token to ensure it&#8217;s valid and corresponds to a user with the right permissions. If everything checks out, you get access.</li>
</ol>



<p class="wp-block-paragraph">In Python, using an authentication token might look something like this:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import requests

url = 'https://example.com/api/data'
token = 'your-authentication-token-here'
headers = {'Authorization': f'Bearer {token}'}

response = requests.get(url, headers=headers)</pre>



<p class="wp-block-paragraph">In this example, the <code>Authorization</code> header is used to pass the token with each request, and the format <code>Bearer {token}</code> is a common way to present the token. The server, upon receiving this request, checks the token and then allows access to the data or functionality you requested.</p>



<h2 class="wp-block-heading">403 Error Reasons</h2>



<p class="wp-block-paragraph">Common reasons for a 403 error include:</p>



<ul class="wp-block-list">
<li><strong>Incorrect URL</strong>: Like dialing a wrong number.</li>



<li><strong>Access Control</strong>: The server&#8217;s way of saying, “You&#8217;re not on the guest list.”</li>



<li><strong>Firewall Settings</strong>: A digital guard blocking your path.</li>



<li><strong>Outdated Cache</strong>: Old information leading you astray.</li>
</ul>



<h2 class="wp-block-heading">Is Web Scraping Legal?</h2>



<p class="wp-block-paragraph">Web scraping walks a fine line between being super helpful and potentially troublesome. It&#8217;s legal if you respect the website&#8217;s terms of service and don&#8217;t overburden their server. </p>



<p class="wp-block-paragraph">Think of it like fishing: do it responsibly, without depleting the fish population! Always check the website&#8217;s <code>robots.txt</code> file to understand their scraping rules.</p>



<p class="wp-block-paragraph"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://blog.finxter.com/is-web-scraping-legal/" data-type="post" data-id="383048">Is Web Scraping Legal?</a></p>
<p>The post <a href="https://blog.finxter.com/solved-python-request-error-403-when-web-scraping/">(Solved) Python Request Error 403 When Web Scraping</a> appeared first on <a href="https://blog.finxter.com">Be on the Right Side of Change</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Improve Your Upwork Job Search with RSS and Data Scraping</title>
		<link>https://blog.finxter.com/improve-your-upwork-job-search-with-rss-and-data-scraping/</link>
		
		<dc:creator><![CDATA[Martin Rhodes]]></dc:creator>
		<pubDate>Tue, 19 Dec 2023 19:25:27 +0000</pubDate>
				<category><![CDATA[Python]]></category>
		<category><![CDATA[Web Development]]></category>
		<category><![CDATA[Web Scraping]]></category>
		<guid isPermaLink="false">https://blog.finxter.com/?p=1653658</guid>

					<description><![CDATA[<p>📝 TLDR Freelancers that use Upwork have an advantage if they apply to job soon after they are posted. Upwork offers an RSS feed that can be parsed for job information sent in the jobs broadcast. Feedparser is a python module that can be used to extract some of the key data from the XML ... <a title="Improve Your Upwork Job Search with RSS and Data Scraping" class="read-more" href="https://blog.finxter.com/improve-your-upwork-job-search-with-rss-and-data-scraping/" aria-label="Read more about Improve Your Upwork Job Search with RSS and Data Scraping">Read more</a></p>
<p>The post <a href="https://blog.finxter.com/improve-your-upwork-job-search-with-rss-and-data-scraping/">Improve Your Upwork Job Search with RSS and Data Scraping</a> appeared first on <a href="https://blog.finxter.com">Be on the Right Side of Change</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="has-global-color-8-background-color has-background wp-block-paragraph"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4dd.png" alt="📝" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>TLDR</strong><br><br>Freelancers that use Upwork have an advantage if they apply to job soon after they are posted.<br><br>Upwork offers an RSS feed that can be parsed for job information sent in the jobs broadcast.<br><br>Feedparser is a python module that can be used to extract some of the key data from the XML data in that RSS field.<br><br>Some of the data in the feed is more deeply embedded and so must be extracted and cleaned before use.<br><br>By combining the extracted data into a Pandas DataFrame there is the ability to filter data and save to a more useful format.</p>



<p class="wp-block-paragraph">At the end of this article, I&#8217;ll provide an interactive Google Colab link for the interactive version of this article. But let&#8217;s start with the video: <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f447.png" alt="👇" class="wp-smiley" style="height: 1em; max-height: 1em;" /> </p>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe title="Improve Your Upwork Job Search with RSS and Data Scraping" width="937" height="527" src="https://www.youtube.com/embed/M6Qp71RnwW8?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>



<h2 class="wp-block-heading">The Coding Challenge</h2>



<p class="wp-block-paragraph">Upwork is seen to be a good platform for potential freelance jobs.</p>



<p class="wp-block-paragraph">But there can be some challenges in getting to the jobs quickly enough. Early applications are frequently the ones accepted.</p>



<p class="wp-block-paragraph">The job search interface is also not very well suited to the filtering and listing of jobs you are looking for.</p>



<p class="wp-block-paragraph">And…<br>Freelancers need to actively search the jobs page!</p>



<p class="wp-block-paragraph">This tutorial and video look at a way to accelerate that search and filter the jobs on preferred criteria.</p>



<h2 class="wp-block-heading">Learning Objectives</h2>



<p class="wp-block-paragraph">By the end of this tutorial, you will have:</p>



<ul class="wp-block-list">
<li>Defined the project requirements</li>



<li>Explored aspects of Data Scraping</li>



<li>Explored RSS feeds and XML</li>



<li>Followed a potentially useful and repeatable workflow</li>



<li>Built a useful tool</li>



<li>Developed some useful Python skills</li>
</ul>



<h2 class="wp-block-heading">Approach</h2>



<p class="wp-block-paragraph">As freelancers, it can be helpful to approach every task as a formal project.<br>It is good practise and you never know when project might become something more valuable.</p>



<p class="wp-block-paragraph">A client may want something similar or it may become a product you can sell.<br>So, it is a good discipline and will save time in the long run to approach such projects professionally.</p>



<h2 class="wp-block-heading">A Useful Workflow</h2>



<p class="wp-block-paragraph">This pattern of development has proven helpful for me:</p>



<ul class="wp-block-list">
<li>Set the project requirements</li>



<li>Follow a sound process for data scraping
<ul class="wp-block-list">
<li>Investigate the data source</li>



<li>Acquire the data</li>



<li>Extract the data you want</li>



<li>Clean the data</li>



<li>Filter the data</li>



<li>Output the data</li>



<li>Use the data and confirm the information is valid</li>
</ul>
</li>



<li>Document the project</li>



<li>Deliver to the client</li>
</ul>



<p class="wp-block-paragraph">And we&#8217;ll follow this process now.</p>



<h2 class="wp-block-heading">Requirements Setting</h2>



<p class="wp-block-paragraph">Just like we do for our clients, we should have specific requirements.</p>



<p class="wp-block-paragraph">I use the MoSCow approach to setting my requirements.</p>



<p class="wp-block-paragraph">This identifies the parts that the project we:</p>



<ul class="wp-block-list">
<li>Must Do</li>



<li>Should Do</li>



<li>Could Do</li>



<li>Won&#8217;t Do</li>
</ul>



<p class="wp-block-paragraph">And sets out clearly what will be delivered and equally as important, what will not be delivered.</p>



<p class="wp-block-paragraph">Our requirements:</p>



<p class="wp-block-paragraph"><strong>MUST</strong>:</p>



<ol class="wp-block-list">
<li>Provide data from Upwork relevant to the Freelancer</li>



<li>Present the information in a readable format</li>
</ol>



<p class="wp-block-paragraph"><strong>SHOULD</strong>:</p>



<ol class="wp-block-list">
<li>Allow filtering and manipulation of the data as needed by the user</li>



<li>Allow for rapid refresh</li>
</ol>



<p class="wp-block-paragraph"><strong>COULD</strong>:</p>



<ol class="wp-block-list">
<li>Run from the command line with arguments</li>



<li>Could be automated</li>
</ol>



<p class="wp-block-paragraph"><strong>WONT</strong>:</p>



<ol class="wp-block-list">
<li>Have a graphical interface</li>
</ol>



<p class="wp-block-paragraph">We are limited on time. So we will focus on the data-scraping aspect of the task. And we will only complete the <strong>Must</strong> and <strong>Should</strong> requirements.</p>



<h2 class="wp-block-heading">Getting the Data</h2>



<h3 class="wp-block-heading">Investigation</h3>



<p class="wp-block-paragraph">We can see here on the Upwork &#8216;search page&#8217;, entering a search term will give you a large number of potential tasks. But we only want some of these and preferably the latest. And we want them filtered to our needs. So we need to defer from what is presented here.</p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="523" src="https://blog.finxter.com/wp-content/uploads/2023/12/Image1-1024x523.png" alt="" class="wp-image-1653661" srcset="https://blog.finxter.com/wp-content/uploads/2023/12/Image1-1024x523.png 1024w, https://blog.finxter.com/wp-content/uploads/2023/12/Image1-300x153.png 300w, https://blog.finxter.com/wp-content/uploads/2023/12/Image1-768x393.png 768w, https://blog.finxter.com/wp-content/uploads/2023/12/Image1-1536x785.png 1536w, https://blog.finxter.com/wp-content/uploads/2023/12/Image1-2048x1047.png 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>
</div>


<p class="wp-block-paragraph">This symbol identifies the Really Simple Syndication (RSS) feed that we shall be using that feed for our data.</p>



<p class="wp-block-paragraph">If we click the link and select RSS, a new page opens with the job feed structured in the Extensible Markup Language (XML).</p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="489" src="https://blog.finxter.com/wp-content/uploads/2023/12/Image2-1024x489.png" alt="" class="wp-image-1653662" srcset="https://blog.finxter.com/wp-content/uploads/2023/12/Image2-1024x489.png 1024w, https://blog.finxter.com/wp-content/uploads/2023/12/Image2-300x143.png 300w, https://blog.finxter.com/wp-content/uploads/2023/12/Image2-768x367.png 768w, https://blog.finxter.com/wp-content/uploads/2023/12/Image2-1536x734.png 1536w, https://blog.finxter.com/wp-content/uploads/2023/12/Image2-2048x979.png 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>
</div>


<p class="wp-block-paragraph">We can also see that the feed format is XML.</p>



<p class="wp-block-paragraph">This is similar to HTML and is a markup language that is readable by the computer and by people (people who can look past the tags and format).</p>



<p class="wp-block-paragraph">Apparently this dense text is &#8216;person&#8217; readable!</p>



<p class="wp-block-paragraph">Some of it seems ok but most is hard to read.</p>



<p class="wp-block-paragraph">Let&#8217;s make this more readable.</p>



<p class="wp-block-paragraph">Time is money for the Freelancer.<br>So let&#8217;s copy this data and use a web tool, an <a href="https://jsonformatter.org/xml-formatter">XML formatter</a> to explore the RSS XML data.</p>



<p class="wp-block-paragraph">Here we can see that the XML forms a tree. With 10 indiviual elements, one for each job in this field.</p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="394" src="https://blog.finxter.com/wp-content/uploads/2023/12/image3-1024x394.png" alt="" class="wp-image-1653663" srcset="https://blog.finxter.com/wp-content/uploads/2023/12/image3-1024x394.png 1024w, https://blog.finxter.com/wp-content/uploads/2023/12/image3-300x115.png 300w, https://blog.finxter.com/wp-content/uploads/2023/12/image3-768x296.png 768w, https://blog.finxter.com/wp-content/uploads/2023/12/image3-1536x591.png 1536w, https://blog.finxter.com/wp-content/uploads/2023/12/image3-2048x788.png 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>
</div>


<p class="wp-block-paragraph">If we look in to the first few items, we can see the information about each job.</p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="389" src="https://blog.finxter.com/wp-content/uploads/2023/12/image4-1024x389.png" alt="" class="wp-image-1653664" srcset="https://blog.finxter.com/wp-content/uploads/2023/12/image4-1024x389.png 1024w, https://blog.finxter.com/wp-content/uploads/2023/12/image4-300x114.png 300w, https://blog.finxter.com/wp-content/uploads/2023/12/image4-768x291.png 768w, https://blog.finxter.com/wp-content/uploads/2023/12/image4-1536x583.png 1536w, https://blog.finxter.com/wp-content/uploads/2023/12/image4-2048x777.png 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>
</div>


<h3 class="wp-block-heading">Observations</h3>



<p class="wp-block-paragraph">It looks like the feed has 10 elements and each element has the Title, Link to the Job and a Description. The description appears to be an HTML script that contains some of the information that we need.</p>



<p class="wp-block-paragraph">So let&#8217;s scrape that data next!</p>



<h3 class="wp-block-heading">Acquisition</h3>



<p class="wp-block-paragraph">There are Python packages that we can use to scrape data from such XML feeds.</p>



<p class="wp-block-paragraph">And it looks like we will be able to extract data from the &#8216;description&#8217; field too. It appears to be a long string object and we have Python mehtods for strings.</p>



<p class="wp-block-paragraph">For the RSS feed with will use &#8216;<a href="https://feedparser.readthedocs.io/en/latest/introduction.html" target="_blank" rel="noreferrer noopener">feedparser&#8217;</a></p>



<p class="has-base-2-background-color has-background wp-block-paragraph"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Note</strong>: For simple data-scraping tasks, I like to use a Jupyter notebook. The notebook is useful because it holds the data in memory so it can be explored while we change the code.</p>



<p class="wp-block-paragraph">This means we don&#8217;t need to capture the feed many times.</p>



<p class="wp-block-paragraph">There is no reason you can&#8217;t use VSCode or Pycharm or any other editor.</p>



<p class="wp-block-paragraph">Again as a freelancer, time is money, so use the tools you are familiar with.</p>



<p class="wp-block-paragraph">Looking at the <a href="https://feedparser.readthedocs.io/en/latest/introduction.html">feedparser documents</a></p>



<p class="wp-block-paragraph">We can learn here that the data is acquired by and then parsed by</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import feedparser
data = feedparser.parse(UPWORK_RSS_FEED_URL)</pre>



<p class="wp-block-paragraph">and then we can extract our 3 elements of data using:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import feedparser
data = feedparser.parse(UPWORK_RSS_FEED_URL)

item_title = data.entries[0].title
item_title
item_link = data.entries[0].link
item_link
description  = data.entries[0].description
description</pre>



<p class="has-base-2-background-color has-background wp-block-paragraph"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Note</strong>: This data will change frequently as the RSS feed is updated with the latest jobs. This is why the RSS feed is so useful to us Finxters.</p>



<h3 class="wp-block-heading">Extraction</h3>



<p class="wp-block-paragraph">We have already extracted the Title and the Link of the job just from the feedparser entries data.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">item_title = data.entries[0].title
item_link = data.entries[0].link</pre>



<p class="wp-block-paragraph">Now we need to extract the individual elements of information from the &#8216;<code>description</code>&#8216; string.</p>



<p class="wp-block-paragraph">Let&#8217;s take a closer look at one of the &#8216;<code>description</code>&#8216; strings.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">description: We are looking for a skilled developer who can create a mobile application and web application for a fitness app. The main feature of the app will be the integration of AI technology to detect the user&amp;#039;s body, diet, and workout plan. The successful candidate will be responsible for designing and developing the app, ensuring it is user-friendly and has a modern, sleek design. The app should be able to track user progress and provide personalized recommendations based on the user&amp;#039;s inputs and body data. Key skills required for this project include: &lt;br />&lt;br />
- Mobile app development &lt;br />
- Web app development &lt;br />
- AI integration &lt;br />
- UX/UI design &lt;br />
- Data analysis and interpretation&lt;br />&lt;br />&lt;b>Hourly Range&lt;/b>: $8.00-$10.00
&lt;br />&lt;b>Posted On&lt;/b>: December 02, 2023 17:57 UTC&lt;br />&lt;b>Category&lt;/b>: Mobile App Development&lt;br />&lt;b>Skills&lt;/b>:iOS, Android, Smartphone, Python, Mobile App Development
&lt;br />&lt;b>Skills&lt;/b>: iOS, Android, Smartphone, Python, Mobile App Development &lt;br />&lt;b>Country&lt;/b>: United States
&lt;br />&lt;a href="https://www.upwork.com/jobs/Fitness-App-Development-with-Functionality_%7E01494dc445d89c9f7f?source=rss">click to apply&lt;/a></pre>



<p class="wp-block-paragraph">Here we see 14 lines of text with HTML markup and tag and characters such as &#8216;<code>br</code>&#8216; and <code>&amp;# 039 ;</code></p>



<p class="wp-block-paragraph">Then we see a selection of headings inside HTML bold tags.</p>



<p class="wp-block-paragraph">So the general theme for the description block is:</p>



<ul class="wp-block-list">
<li><strong>description </strong>&#8211; HTML code of variable lengths and with some HTML character codes and tags</li>



<li><strong>Hourly Range</strong> &#8211; <code>b_tags</code> and some text</li>



<li><strong>Posted On</strong> &#8211; <code>b_tags</code> and some text</li>



<li><strong>Category</strong> &#8211; <code>b_tags</code> and some text</li>



<li><strong>Skills</strong> &#8211; <code>b_tags</code> and 1 or more skills with commas and spaces in between</li>



<li><strong>Skills </strong>&#8211; a repeated line of skills</li>



<li><strong>Country </strong>&#8211; <code>b_tags</code> and some text</li>



<li><strong>Link </strong>&#8211; a repeat of the link</li>
</ul>



<p class="wp-block-paragraph">Knowing this data structure, we can now use Python to extract the information we need.</p>



<p class="wp-block-paragraph">Let&#8217;s write some code!</p>



<p class="wp-block-paragraph">First we need to import some packages.</p>



<ul class="wp-block-list">
<li><code>feedparser</code> for the RSS feed.</li>



<li><code>pandas</code> for our data storage and filtering</li>



<li><code>ssl</code> to bypass some <code>ssl</code> elements of the feed <code>broadcast</code>.</li>
</ul>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import feedparser
import pandas as pd
import ssl</pre>



<p class="wp-block-paragraph">Now we need a function to create and return an empty and prepared Dataframe in Pandas.</p>



<p class="wp-block-paragraph">As we&#8217;ve discussed, we need to store each jobs:</p>



<ul class="wp-block-list">
<li>Title</li>



<li>Link</li>



<li>Description</li>



<li>Posted on</li>



<li>Category</li>



<li>Skills List</li>



<li>Price Type (Hourly Range or Budget</li>



<li>Price of budget of max Hourly Rate</li>



<li>Country the job originates in</li>
</ul>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">def make_dataframe():

    jobs_df = pd.DataFrame(columns=[
        'Title', 
        'Link',
        'Description', 
        'Posted',
        'Category',
        'Skills',
        'Price Type', 
        'Price', 
        'Country' ])

    return jobs_df</pre>



<p class="wp-block-paragraph">We need a function that steps through the feed and extracts our information.</p>



<p class="wp-block-paragraph">Firstly, we set up a list of blank data so that if there are gaps in the information, we still have data to place in the DataFrame. Failure to do this would raise an error.</p>



<p class="wp-block-paragraph">Then we &#8216;Parse the Feed&#8217;</p>



<p class="wp-block-paragraph">Title and Job link we can get directly from the feed entry.</p>



<p class="wp-block-paragraph">But for the &#8216;<code>description</code>&#8216;, we need to use the &#8216;<code><a href="https://blog.finxter.com/python-string-split/" data-type="post" data-id="26097">string.split</a></code>&#8216; method and split the string into a list of elements using the &#8216;<code>bold</code>&#8216; tag as the separator.</p>



<p class="wp-block-paragraph">This gives us:</p>



<ul class="wp-block-list">
<li><code>description[0]</code> is the first item in the list and is the main description field we just need to stip this of HTML tags Here we use the &#8216;<code>clean_string</code>&#8216; function.</li>



<li>&#8216;Posted On&#8217; and &#8216;Category&#8217; also get cleaned with &#8216;<code>clean_string</code>&#8216;.</li>
</ul>



<p class="wp-block-paragraph"><em>Notice that we slice off only that part we need to send to be &#8216;cleaned&#8217; eg <code>clean_string(b_tag[15:]</code>)</em></p>



<ul class="wp-block-list">
<li>&#8216;Hourly Range&#8217; / Budget&#8217; get special treatment in the &#8216;clean_price&#8217; function where we return a float for the money value and a string for &#8216;Budget&#8217; or &#8216;Hourly Rate&#8217;</li>



<li>&#8216;Skills&#8217; needs to be stripped into a list (for searching) and also cleaned.</li>



<li>&#8216;Country&#8217; also needs some special treatment</li>
</ul>



<p class="wp-block-paragraph">Once cleaned the data is assigned to a dictionary and added to a DataFrame and added to the master DataFrame.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">def get_data(entry):  # entry is a job item from the RSS feed

    # Some data ends up Null so set those values just in case
    item_posted = ''
    item_cat = ''
    item_price_type = ''
    item_price = 0.0
    item_skills = []
    item_country = ''

    # Set from parsing the feed
    item_title = entry.title
    item_link = entry.link
    description  = entry.description
    description = description.split('&lt;b>')
    item_desc = clean_string(description[0])
    for b_tag in description[1:]:
        if "Hourly Range" in b_tag or "Budget" in b_tag:
            item_price_type, item_price = clean_price(b_tag)
        elif "Posted On" in b_tag:
            item_posted = clean_string(b_tag[15:])
        elif "Category" in b_tag:
            item_cat = clean_string(b_tag[14:])
        elif "Skills" in b_tag and not item_skills :
            item_skills = clean_skills(b_tag[11:])
        elif "Country" in b_tag:
            item_country = clean_country(b_tag[10:])




    # build the DataFrame and return it

    new_job = {
    'Title': item_title, 
    'Link': item_link, 
    'Description': item_desc,
    'Posted': item_posted,
    'Category': item_cat, 
    'Skills': item_skills, 
    'Price Type': item_price_type, 
    'Price': item_price, 
    'Country': item_country}

    new_job_df = pd.DataFrame([new_job])

    return new_job_df</pre>



<p class="wp-block-paragraph">The &#8216;<code>clean_string</code>&#8216; function uses the &#8216;<code>replace</code>&#8216; method and takes each substring that isn&#8217;t required and either removes it or replaces it with the correct value.</p>



<h5 class="wp-block-heading has-base-2-background-color has-background"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Note: This is not the most pythonic approach, but it has been written for clarity for beginners in mind. How would you make it more Pythonic?</h5>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">def clean_string(string):

    string = string.replace('&lt;br />','')
    string = string.replace('&lt;/b>','')
    string = string.replace('&amp;nbsp;','')
    string= string.replace('&amp;#039;','\'')
    string = string.replace('&amp;rsquo;','\'')
    string = string.replace('&amp;ldquo;','\"') 
    string = string.replace('&amp;rdquo;','\"') 
    string = string.replace('quot;','\'')
    string = string.strip()

    return string</pre>



<p class="wp-block-paragraph">The &#8216;<code>clean_price</code>&#8216; function splits the identifier (&#8216;Hourly Range&#8217; or &#8216;Budget&#8217;) into a new string.</p>



<p class="wp-block-paragraph">It then extracts the number (also a string) and returns it as a float along wth the identifier.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">def clean_price(item_Bud_HR):

    price_split = item_Bud_HR.split(':')
    item_price_type = clean_string(price_split[0]) 
    item_price = price_split[1] # Get and clean the value
    item_price = item_price.replace('$','')
    item_price = item_price.replace('&lt;br />','')
    item_price = item_price.strip()
    if '-' in item_price:
        item_price = item_price.split('-') # If the price is an 'Hourly Range' we split, returning the number on the right of  '-'  
        item_price = item_price[1]
    item_price = float(item_price)

    return item_price_type, item_price</pre>



<p class="wp-block-paragraph">The &#8216;<code>clean_country</code>&#8216; function splits the string on <code>'\n'</code>. It then takes the first element, cleans off the white space and returns the Country name.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">def clean_country(item_country):

    item_country = item_country.split('\n')
    item_country = clean_string(item_country[0])
    item_country = item_country[1].strip()

    return item_country</pre>



<p class="wp-block-paragraph">The &#8216;<code>clean_skills</code>&#8216; function is a little more complex.</p>



<p class="wp-block-paragraph">We create a new empty list, &#8216;<code>item_skills_list</code>&#8216;.</p>



<p class="wp-block-paragraph">We then clean the string by removing HTML tags.</p>



<p class="wp-block-paragraph">We split the string on the <code>','</code> character and step through the list that is created, cleaning each string and then appending it to the list before it is returned.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">def clean_skills(item_skills):

    item_skills_list =[]
    item_skills = item_skills.replace('&lt;br />','')
    item_skills = item_skills.split(',')
    for skill in item_skills:
        item_skills_list.append(skill.strip())

    return item_skills_list</pre>



<p class="wp-block-paragraph">Once a new job DataFrame is created for each job, it is &#8216;concatenated&#8217; to the master DataFrame for later filtering.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">def join_dataframes(new_item_df,jobs_df):

    jobs_df = pd.concat([jobs_df, new_item_df], ignore_index=True)

    return jobs_df</pre>



<p class="wp-block-paragraph">The DataFrame <code>jobs_df</code> now holds all of the RSS feed jobs and their associated data. We can now filter it as required.</p>



<p class="wp-block-paragraph">The ones I have presented here (commented out) offer examples for your own filters.</p>



<ol class="wp-block-list">
<li>Strips out any duplicates based on the &#8216;Posted&#8217; time.</li>



<li>Looks for budgets and hourly figures above 20.0 dollars</li>



<li>Looks for selected countries (United States and India)</li>
</ol>



<p class="wp-block-paragraph">What would you want to filter for?</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">def filter_output(jobs_df):


    # FILTER THE DATA USING Pandas

# 1. Strip out non unique values - Posted on is the pseudo-primary key
    # Uncomment if needed
    #jobs_df = jobs_df.drop_duplicates(subset=['Posted'], ignore_index=True) 

 #2. Only save for Price or budget greater than $10
    # Uncomment if needed
    #jobs_df = jobs_df[jobs_df['Price'] > 20] 

# 3. Only save for Specific Country 
    # selecting rows based on condition 
    # Uncomment below if needed
    #options = ['United States', 'India'] 
    #jobs_df = jobs_df[jobs_df['Country'].isin(options)]

    return jobs_df</pre>



<p class="wp-block-paragraph">Here we have the <code>main()</code> function that takes the Upwork RSS URL and feeds it to the function in turn.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""># MAIN
def main():

# Why SSL
# Python is adding http verification in in the std library
# This bypasses the check for th moment
    if hasattr(ssl, '_create_unverified_context'):
        ssl._create_default_https_context = ssl._create_unverified_context


    url="https://www.upwork.com/ab/feed/jobs/rss?q=Python&amp;sort=recency&amp;paging=0%3B10&amp;api_params=1&amp;securityToken=6b9f07dc2632b4ac772d5daa37626af471b7d2526826c56a0c16aad6580245646f4e13804c72bd1ed3755f3bd552f5ba1d3f67a021987f714a1ff340ba7659dc&amp;userUid=1215586676591329280&amp;orgUid=1215586676603912193"

    #Get the Feed Data
    data = feedparser.parse(url)

    # Make the master dataframe
    jobs_df = make_dataframe()

    #Get the data for each item and add it to the DataFrame
    for entry in data.entries:
        new_job_df = get_data(entry)
        # join the new dataframe to the list
        jobs_df = join_dataframes(new_job_df, jobs_df)</pre>



<p class="wp-block-paragraph">Now we have the jobs from the RSS feed in a DataFrame, we can filter using the pandas methods.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">    filter_output(jobs_df)</pre>



<figure class="wp-block-table is-style-stripes"><table><thead><tr><th></th><th>Title</th><th>Link</th><th>Description</th><th>Posted</th><th>Category</th><th>Skills</th><th>Price Type</th><th>Price</th><th>Country</th></tr></thead><tbody><tr><th>0</th><td>AWS Python Consultant &#8211; Upwork</td><td>https://www.upwork.com/jobs/AWS-Python-Consult&#8230;</td><td>Fluent English speaking Python developer with &#8230;</td><td>December 02, 2023 20:13 UTC</td><td>DevOps Engineering</td><td>[Ubuntu, Amazon Web Services, Python, AWS Lamb&#8230;</td><td>Hourly Range</td><td>10.0</td><td>United Kingdom</td></tr><tr><th>1</th><td>1min Time Frame Forex Scalper &#8211; Upwork</td><td>https://www.upwork.com/jobs/1min-Time-Frame-Fo&#8230;</td><td>If you scalp the forex market on the m1 time f&#8230;</td><td>December 02, 2023 20:13 UTC</td><td>Deep Learning</td><td>[Forex Trading]</td><td>Hourly Range</td><td>40.0</td><td>United Kingdom</td></tr><tr><th>2</th><td>AI &#8211; driven crypto charting project &#8211; Upwork</td><td>https://www.upwork.com/jobs/driven-crypto-char&#8230;</td><td>Scope of work\nThedevelopment of a crypto char&#8230;</td><td>December 02, 2023 20:03 UTC</td><td>Machine Learning</td><td>[Artificial Intelligence, Machine Learning, Bl&#8230;</td><td>Hourly Range</td><td>40.0</td><td>Nigeria</td></tr><tr><th>3</th><td>Gelato Smart Contract Integration Upgrade &#8211; Up&#8230;</td><td>https://www.upwork.com/jobs/Gelato-Smart-Contr&#8230;</td><td>I&#8217;m looking for a Solidity developer with Foun&#8230;</td><td>December 02, 2023 20:01 UTC</td><td>Emerging Tech</td><td>[Solidity, Blockchain, TypeScript, Ethereum]</td><td>Hourly Range</td><td>40.0</td><td>United States</td></tr><tr><th>4</th><td>Publish Open-source AI Agent to Web UI (Flutte&#8230;</td><td>https://www.upwork.com/jobs/Publish-Open-sourc&#8230;</td><td>The goal of this project is to create a web UI&#8230;</td><td>December 02, 2023 20:01 UTC</td><td>Full Stack Development</td><td>[AI Agent Development, AI App Development, Flu&#8230;</td><td>Budget</td><td>100.0</td><td>Canada</td></tr><tr><th>5</th><td>Price check automation &#8211; Upwork</td><td>https://www.upwork.com/jobs/Price-check-automa&#8230;</td><td>Would like one of the experts to build me a bo&#8230;</td><td>December 02, 2023 19:57 UTC</td><td>Scripting &amp;amp; Automation</td><td>[Automation, Data Scraping, Data Mining, Data &#8230;</td><td>Hourly Range</td><td>100.0</td><td>Saudi Arabia</td></tr><tr><th>6</th><td>Need for Good Hackers to Assist in Scamming Si&#8230;</td><td>https://www.upwork.com/jobs/Need-for-Good-Hack&#8230;</td><td>We are looking for good hackers who can assist&#8230;</td><td>December 02, 2023 19:38 UTC</td><td>Information Security</td><td>[Data Entry, Python]</td><td>Hourly Range</td><td>45.0</td><td>United States</td></tr><tr><th>7</th><td>Microservices Architecture Help &#8211; Upwork</td><td>https://www.upwork.com/jobs/Microservices-Arch&#8230;</td><td>### **The Data Synchronization Dilemma**\n&#8212;\&#8230;</td><td>December 02, 2023 19:35 UTC</td><td>Back-End Development</td><td>[Python, Microservice, Software Architecture &amp;&#8230;</td><td>Hourly Range</td><td>40.0</td><td>India</td></tr><tr><th>8</th><td>Build two AVL trees for project &#8211; Upwork</td><td>https://www.upwork.com/jobs/Build-two-AVL-tree&#8230;</td><td>I need an avl tree to hold a string node (key)&#8230;</td><td>December 02, 2023 19:28 UTC</td><td>Full Stack Development</td><td>[C++]</td><td>Budget</td><td>250.0</td><td>United States</td></tr><tr><th>9</th><td>ROMP texture on 3D SMPL mesh using Pytorch (No&#8230;</td><td>https://www.upwork.com/jobs/ROMP-texture-SMPL-&#8230;</td><td>(WARNING to SCAMMER)\nStarting from an existin&#8230;</td><td>December 02, 2023 19:23 UTC</td><td>AR/VR Design</td><td>[Python, PyTorch, Augmented Reality, Linux, Ub&#8230;</td><td>Budget</td><td>300.0</td><td>Germany</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">Once we have filtered the data to meet our needs, we use the pandas method to save the DataFrame to an Excel file.</p>



<p class="wp-block-paragraph">It also prints out the top 3 entries to demonstrate the data has been captured.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">    print(jobs_df.head(3))
    # Export to excel
    jobs_df.to_excel('jobs.xlsx', index=False)</pre>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">                                          Title  \
0                AWS Python Consultant - Upwork   
1        1min Time Frame Forex Scalper - Upwork   
2  AI - driven crypto charting project - Upwork   

                                                Link  \
0  https://www.upwork.com/jobs/AWS-Python-Consult...   
1  https://www.upwork.com/jobs/1min-Time-Frame-Fo...   
2  https://www.upwork.com/jobs/driven-crypto-char...   

                                         Description  \
0  Fluent English speaking Python developer with ...   
1  If you scalp the forex market on the m1 time f...   
2  Scope of work\nThedevelopment of a crypto char...   

                        Posted            Category  \
0  December 02, 2023 20:13 UTC  DevOps Engineering   
1  December 02, 2023 20:13 UTC       Deep Learning   
2  December 02, 2023 20:03 UTC    Machine Learning   

                                              Skills    Price Type  Price  \
0  [Ubuntu, Amazon Web Services, Python, AWS Lamb...  Hourly Range   10.0   
1                                    [Forex Trading]  Hourly Range   40.0   
2  [Artificial Intelligence, Machine Learning, Bl...  Hourly Range   40.0   

          Country  
0  United Kingdom  
1  United Kingdom  
2         Nigeria  </pre>



<p class="wp-block-paragraph">That completes our exploration of the code.</p>



<p class="wp-block-paragraph">I hope you have found some insight and value here.</p>



<p class="wp-block-paragraph">Let us review.</p>



<h2 class="wp-block-heading">Learning Objectives</h2>



<p class="wp-block-paragraph">This tutorial and video looked at how to read the RSS feed from Upwork, to accelerate your search and allow you to filter the jobs on your preferred criteria.</p>



<p class="wp-block-paragraph">We have covered:</p>



<ul class="wp-block-list">
<li>Defining your project requirements</li>



<li>Data Scraping</li>



<li>RSS feeds and XML (briefly)</li>



<li>A potentially useful workflow</li>



<li>The building of a useful tool</li>



<li>Some useful Python skills</li>
</ul>



<h2 class="wp-block-heading">Next Steps</h2>



<p class="wp-block-paragraph">This code is very flexible and so here are some options you might want to consider if you are extending its utility:</p>



<ul class="wp-block-list">
<li>You may want to run this code on a timer to give you frequent updates.</li>



<li>You may also want to load the previous jobs scraped into the <code>jobs_df</code> DataFrame so that you can append new jobs.</li>



<li>You may also want to have a <em>&#8216;list of urls&#8217;</em> for different searches that you step through in order to cover lots of searches</li>



<li>If you searches are very specific you might want to have the script email you when a job is posted.</li>
</ul>



<p class="wp-block-paragraph">What will you do?</p>



<h3 class="wp-block-heading">Resources:</h3>



<ul class="wp-block-list">
<li>https://jsonformatter.org/xml-formatter</li>



<li>https://ascii.cl/htmlcodes.htm</li>
</ul>



<p class="wp-block-paragraph">You can also check out this guide on Google Colab using <a href="https://colab.research.google.com/drive/19lIMTWTHw83Kv8JyvXbeAry6yiDrlGqT?usp=sharing" data-type="link" data-id="https://colab.research.google.com/drive/19lIMTWTHw83Kv8JyvXbeAry6yiDrlGqT?usp=sharing">this link</a>.</p>
<p>The post <a href="https://blog.finxter.com/improve-your-upwork-job-search-with-rss-and-data-scraping/">Improve Your Upwork Job Search with RSS and Data Scraping</a> appeared first on <a href="https://blog.finxter.com">Be on the Right Side of Change</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>How to Scrape Wikipedia Pages to Create a Question-Answer Database?</title>
		<link>https://blog.finxter.com/how-to-scrape-wikipedia-pages-to-create-a-question-answer-database/</link>
		
		<dc:creator><![CDATA[Gábor Madarász]]></dc:creator>
		<pubDate>Tue, 19 Dec 2023 12:29:59 +0000</pubDate>
				<category><![CDATA[Artificial Intelligence]]></category>
		<category><![CDATA[Large Language Model (LLM)]]></category>
		<category><![CDATA[Projects]]></category>
		<category><![CDATA[Prompt Engineering]]></category>
		<category><![CDATA[Web Scraping]]></category>
		<guid isPermaLink="false">https://blog.finxter.com/?p=1653652</guid>

					<description><![CDATA[<p>Why Create a QA Database? A good question often opens up new perspectives and new ways of thinking. But that&#8217;s not why I had to create a Question-Answering database. Question-Answering (QA) databases play an important role in researching and developing large language models (LLMs). These databases have two primary applications: Traditionally, QA databases can be ... <a title="How to Scrape Wikipedia Pages to Create a Question-Answer Database?" class="read-more" href="https://blog.finxter.com/how-to-scrape-wikipedia-pages-to-create-a-question-answer-database/" aria-label="Read more about How to Scrape Wikipedia Pages to Create a Question-Answer Database?">Read more</a></p>
<p>The post <a href="https://blog.finxter.com/how-to-scrape-wikipedia-pages-to-create-a-question-answer-database/">How to Scrape Wikipedia Pages to Create a Question-Answer Database?</a> appeared first on <a href="https://blog.finxter.com">Be on the Right Side of Change</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">Why Create a QA Database?</h2>



<p class="wp-block-paragraph">A good question often opens up new perspectives and new ways of thinking. But that&#8217;s not why I had to create a <strong><em>Question-Answering database</em></strong>.</p>



<p class="wp-block-paragraph">Question-Answering (QA) databases play an important role in researching and developing <a href="https://blog.finxter.com/the-evolution-of-large-language-models-llms-insights-from-gpt-4-and-beyond/" data-type="post" data-id="1267220">large language models (LLMs)</a>. These databases have two primary applications:</p>



<ul class="wp-block-list">
<li>As training material</li>



<li>To evaluate results</li>
</ul>



<p class="wp-block-paragraph">Traditionally, QA databases can be produced in two main ways:</p>



<ul class="wp-block-list">
<li><strong>Manual compilation</strong>: experts or volunteers collect questions and answers, ensuring high quality and accuracy. (But slow and expensive)</li>



<li><strong>Crowdsourcing</strong>: using the public to collect questions and answers. (Quicker but less quality)</li>
</ul>



<p class="wp-block-paragraph">However, with today&#8217;s modern (<em>Instruction Tuned</em>) language models, it is possible to quickly generate high-quality databases at a low cost.</p>



<p class="wp-block-paragraph">I could not find a suitable database in my language (Hungarian), so I had to create one.</p>



<p class="wp-block-paragraph">In this article, I am creating a question-answer database based on Wikipedia pages determined by keywords. For this, I am using the <a href="https://blog.finxter.com/fine-tuning-gpt-3-5-turbo-how-to-craft-your-own-proprietary-llm/" data-type="post" data-id="1650906">OpenAI GPT-3.5 turbo</a> model and the Wikipedia Python module.</p>



<h2 class="wp-block-heading">How I Did It</h2>



<p class="wp-block-paragraph">After loading the necessary modules and setting the logging level (<code>level=logging.DEBUG</code> reports detailed information about the system&#8217;s operation, set this to <code>"logging.INFO"</code> if you don&#8217;t need this level of monitoring), we <a href="https://blog.finxter.com/openai-python-api-a-helpful-illustrated-guide-in-5-steps/" data-type="link" data-id="https://blog.finxter.com/openai-python-api-a-helpful-illustrated-guide-in-5-steps/">set up the Openai API</a>. </p>



<p class="wp-block-paragraph">Do not forget to provide your API key!</p>



<p class="wp-block-paragraph">I statically encoded the instruction into the &#8220;<code>messages</code>&#8221; variable and everyone should adapt it to their own language. It is also worth trying different prompts for the best results. </p>



<p class="wp-block-paragraph">I used this one: <code>"Write 1 relevant question in Hungarian about the following text!"</code></p>



<pre class="EnlighterJSRAW" data-enlighter-language="python" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""># GPT 3.5 Turbo API Client
class GPT3ChatClient:
    def __init__(self, api_key='sk-YOUR_API_KEY'):
        self.api_key = api_key
        self.headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json"
        }
        self.endpoint = "https://api.openai.com/v1/chat/completions"

    def query(self, query_string, max_tokens=100, temperature=0.5, **kwargs):
        messages = [{"role": "user", "content": "Írj 1 releváns magyar nyelvű kérdést a következő szövegről! " + query_string}]     #You have to customize "content" to your language!
        payload = {
            "model": "gpt-3.5-turbo",
            "messages": messages,
            "max_tokens": max_tokens,
            "temperature": temperature,
            **kwargs
        }
        response = requests.post(self.endpoint, json=payload, headers=self.headers)
        response_data = response.json()
        return response_data['choices'][0]['message']['content'] if response_data.get('choices') else None
</pre>



<p class="wp-block-paragraph">I have defined a function that transforms Hungarian accented characters, which will be necessary for automatic file name generation. (It’s completely optional!) </p>



<p class="wp-block-paragraph">It uses the <code>str.replace(char, new_char)</code> function on a predefined <a href="https://blog.finxter.com/python-create-dictionary-the-ultimate-guide/" data-type="post" data-id="1651200">dictionary</a> of characters.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""># Utility function for string replacement
def replace_accented_chars(input_str):
    replacements = {
        'á': 'a', 'é': 'e', 'í': 'i', 'ó': 'o', 'ö': 'o',
        'ő': 'o', 'ú': 'u', 'ü': 'u', 'ű': 'u', ' ': '_'
    }
    for accented_char, replacement in replacements.items():
        input_str = input_str.replace(accented_char, replacement)
    return input_str.lower()</pre>



<p class="wp-block-paragraph">The following function performs Wikipedia scraping with attention to the &#8220;<code>results=2</code>&#8221; parameter, which determines how many pages to search based on the keyword. </p>



<p class="wp-block-paragraph">The second argument of the function (<code>language="hu"</code>) is the language code. Set prefix to one of the two letter prefixes on the<a href="http://meta.wikimedia.org/wiki/List_of_Wikipedias" target="_blank" rel="noreferrer noopener"> list of all Wikipedias</a>.</p>



<p class="wp-block-paragraph">I have integrated error handling into the function, and it uses &#8220;<code><a href="https://blog.finxter.com/how-to-randomly-sample-from-a-python-list/" data-type="post" data-id="719846">random.choice</a></code>&#8221; to randomly select a page when a keyword would raise a <code>DisambiguationError</code> if the page is a disambiguation page.</p>



<p class="wp-block-paragraph">The <code>wikipedia.search()</code> function has a “<code>suggestion</code>” argument, if <code>True</code>, it returns the results and suggestion (if any) in a tuple. I have not used this.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""># Wikipedia scraping based on keywords
def scrape_wikipedia(keyword, language="hu"):
    wikipedia.set_lang(language)
    merged_text = []
    result = wikipedia.search(keyword, results=2, suggestion=False)

    for item in result:
        try:
            page = wikipedia.page(item, auto_suggest=False)
        except wikipedia.DisambiguationError as e:
            selected_option = random.choice(e.options)
            logging.debug(f"DisambiguationError: {selected_option}, {e}")
            page = wikipedia.page(selected_option)

        content = page.content
        merged_text.append(content)

    return merged_text</pre>



<p class="wp-block-paragraph">Defining some basic text cleaning: </p>



<p class="wp-block-paragraph">In this section, I filter out non-alphanumeric (Hungarian) characters and replace the <code>"\n"</code> (newline) characters with spaces. To filter out short sentences, we only keep sentences longer than 5 words (Feel free to customize it!). </p>



<p class="wp-block-paragraph">For an advanced LLM, it is advisable to provide a relatively longer context; this helps in generating better questions.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""># Cleaning data
def clean_text(string_list):
    allowed_chars = re.compile('[a-zA-ZáÁéÉíÍóÓöÖőŐúÚüÜűŰ\s.:;,%?!0-9-]') #Allow only hungarian characters
    clean_list = [''.join(allowed_chars.findall(string.replace("\n", " "))) for string in string_list if len(string.split()) > 5]
    return clean_list</pre>



<p class="wp-block-paragraph">After that we initialize the GPT-3.5 <code>ChatClient</code> using an API key. </p>



<p class="wp-block-paragraph">The API key grants access to the GPT service. We load a <strong>Spacy language processing model</strong> for the Hungarian language. </p>



<p class="wp-block-paragraph">&#8220;<code>hu_core_news_lg</code>&#8221; is a pretrained model for the Hungarian language. Check if the Spacy model has a &#8220;<code>sentencizer</code>&#8221; factory, which is responsible for sentence splitting.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""># Initialize GPT client and Spacy model
# api_key='sk-YOUR_API_KEY'
gpt_client = GPT3ChatClient(api_key) 
nlp = spacy.load("hu_core_news_lg")
assert nlp.has_factory("sentencizer")
</pre>



<p class="wp-block-paragraph">I break down these pages into sentences using huSpacy&#8217;s <a href="https://blog.finxter.com/transformer-vs-convolutional-neural-net-cnn/" data-type="post" data-id="1651359">CNN</a>-based large model, but you can also get acceptable results using the following regex code (if no <code>spacy</code> model is available):</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""># Regular expression pattern for splitting text into sentences
def split_list_into_sentences(text_list):
    pattern = r'(?&lt;!\w\.\w.)(?&lt;![A-Z][a-z]\.)(?&lt;=\.|\?|!)\s'
    sentences_list = []
    for text in text_list:
        sentences = re.split(pattern, text)
        sentences_list.extend(sentences)
    return sentences_list</pre>



<p class="wp-block-paragraph">Here, we set the keyword that we&#8217;ll use later to search for Wikipedia pages:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">#Set the keyword for wikipedia search
keyword = "OpenAI" 
</pre>



<p class="wp-block-paragraph">After that,</p>



<ul class="wp-block-list">
<li>We use the <code>scrape_wikipedia</code> function to fetch Wikipedia pages based on the provided keyword. The <code>clean_text</code> function is used to clean and preprocess the retrieved text.</li>



<li><code>doc = nlp(str(cleaned_text))</code>: We use Spacy to process the cleaned text, splitting it into individual sentences for further analysis.</li>



<li><code>data = {...}</code>: We create a data table that contains the processed sentences and queries to be asked to GPT-3.5. The queries are the sentences that we&#8217;ll ask ChatGPT about. This line creates a dictionary with two keys: <code>'Query'</code> and <code>'Answer'</code>. The values are lists generated using <a href="https://blog.finxter.com/list-comprehension-in-python/" data-type="post" data-id="1646570">list comprehension</a>.<br><code>'Query'</code>: In this part of the code, a <code>gpt_client</code> object is used, and the query function is called for each sentence (<code>sent.text</code>) in the processed document (<code>doc.sents</code>).<br>The result is a list containing questions from the LLM.<br><code>'Answer'</code>: In this part of the code, each sentence from <code>doc.sents</code>, which was processed earlier, is simply inserted into a list. The result is a list containing all the sentences.</li>



<li><code>df = pd.DataFrame(data)</code>: We <a href="https://blog.finxter.com/how-to-create-a-dataframe-in-pandas/" data-type="post" data-id="16764">create a Pandas DataFrame</a> based on the data table created in the previous step.</li>



<li><code>print(f"{len(df)} questions generated.")</code>: We print the number of generated questions.</li>
</ul>



<p class="wp-block-paragraph">In a corresponding prompt (You have to customize it for your language!), I generated questions for these answers, and then put the data into a DataFrame, from where it can be saved as a file and sent to, for example, human annotators or used for various projects.</p>



<p class="wp-block-paragraph">Such a project could be, for example, an evaluation of a model&#8217;s &#8220;factual&#8221; knowledge on a given topic. By running through the generated questions, the existing &#8220;gold&#8221; answers can be compared with the answers given by the model.</p>



<p class="wp-block-paragraph">Another, more fun use of the database could be, for example, to quickly set up quiz games.</p>



<p class="wp-block-paragraph">For example, the DataFrame can be exported to an <code>xlsx</code> file to a given path:<br></p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""># Write result to excel file
output_folder = "./results"
    if not os.path.exists(output_folder):    # create “results” folder if not exist
        os.makedirs(output_folder)
filename = replace_accented_chars(keyword)
df.to_excel(f"{output_folder}/{filename}.xlsx")
</pre>



<p class="wp-block-paragraph">The result xlsx:<br></p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="274" src="https://blog.finxter.com/wp-content/uploads/2023/12/screen1-1024x274.png" alt="" class="wp-image-1653654" srcset="https://blog.finxter.com/wp-content/uploads/2023/12/screen1-1024x274.png 1024w, https://blog.finxter.com/wp-content/uploads/2023/12/screen1-300x80.png 300w, https://blog.finxter.com/wp-content/uploads/2023/12/screen1-768x205.png 768w, https://blog.finxter.com/wp-content/uploads/2023/12/screen1-1536x411.png 1536w, https://blog.finxter.com/wp-content/uploads/2023/12/screen1.png 1796w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>
</div>


<h2 class="wp-block-heading">Conclusion</h2>



<p class="wp-block-paragraph">The created database contains approximately clean and relevant questions but, of course, it can be further refined (and is worth refining) with human effort, but this already involves much less effort than coming up with the questions from scratch.</p>



<p class="wp-block-paragraph">Further improvement of the results can be achieved with additional prompting techniques, for example, by providing a system role to guide the language model toward the specific topic.</p>



<p class="wp-block-paragraph">With the rise of large language models, many tasks are becoming faster, and considering that these models truly excel when working with texts, efficient code can be crafted for such work.</p>



<p class="has-base-2-background-color has-background wp-block-paragraph"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Related Blog</strong>: <a href="https://blog.finxter.com/prompt-engineering-with-llama-2-full-course/" data-type="link" data-id="https://blog.finxter.com/prompt-engineering-with-llama-2-full-course/">Prompt Engineering with Llama 2 (Full Course)</a></p>



<h2 class="wp-block-heading">Full Code (Appendix)</h2>



<p class="wp-block-paragraph">Here is the full code:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">#!/usr/bin/env python
# coding: utf-8

"""
Q&amp;A Generator with GPT3.5-turbo from Wikipedia pages based on keywords.
"""

# Required libraries
import logging
import sys
import requests
import wikipedia
import random
import warnings
import re
import spacy
import huspacy
import pandas as pd
import os

# Logging configuration
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)

# GPT 3.5 Turbo API Client
class GPT3ChatClient:
    def __init__(self, api_key='sk-YOUR_API_KEY'):
        self.api_key = api_key
        self.headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json"
        }
        self.endpoint = "https://api.openai.com/v1/chat/completions"

    def query(self, query_string, max_tokens=100, temperature=0.5, **kwargs):
        messages = [{"role": "user", "content": "Írj 1 releváns magyar nyelvű kérdést a következő szövegről! " + query_string}]     #You have to customize "content" to your language
        payload = {
            "model": "gpt-3.5-turbo",
            "messages": messages,
            "max_tokens": max_tokens,
            "temperature": temperature,
            **kwargs
        }
        response = requests.post(self.endpoint, json=payload, headers=self.headers)
        response_data = response.json()
        return response_data['choices'][0]['message']['content'] if response_data.get('choices') else None

# Utility function for string replacement
def replace_accented_chars(input_str):
    replacements = {
        'á': 'a', 'é': 'e', 'í': 'i', 'ó': 'o', 'ö': 'o',
        'ő': 'o', 'ú': 'u', 'ü': 'u', 'ű': 'u', ' ': '_'
    }
    for accented_char, replacement in replacements.items():
        input_str = input_str.replace(accented_char, replacement)
    return input_str.lower()

# Wikipedia scraping based on keywords
def scrape_wikipedia(keyword, language="hu"):
    wikipedia.set_lang(language)
    merged_text = []
    result = wikipedia.search(keyword, results=2, suggestion=False)

    for item in result:
        try:
            page = wikipedia.page(item, auto_suggest=False)
        except wikipedia.DisambiguationError as e:
            selected_option = random.choice(e.options)
            logging.debug(f"DisambiguationError: {selected_option}, {e}")
            page = wikipedia.page(selected_option)

        content = page.content
        merged_text.append(content)

    return merged_text

# Cleaning data
def clean_text(string_list):
    allowed_chars = re.compile('[a-zA-ZáÁéÉíÍóÓöÖőŐúÚüÜűŰ\s.:;,%?!0-9-]')
    clean_list = [''.join(allowed_chars.findall(string.replace("\n", " "))) for string in string_list if len(string.split()) > 5]
    return clean_list

# Initialize GPT client and Spacy model
gpt_client = GPT3ChatClient(api_key='sk-')    #api_key='sk-YOUR_API_KEY'
nlp = spacy.load("hu_core_news_lg")
assert nlp.has_factory("sentencizer")

# Process keyword
keyword = "OpenAI"
cleaned_text = clean_text(scrape_wikipedia(keyword, language="hu"))    #Set the language code from here https://meta.wikimedia.org/wiki/List_of_Wikipedias

# Sentence splitting and DataFrame creation
doc = nlp(str(cleaned_text))
data = {
    'Query': [gpt_client.query(sent.text) for sent in doc.sents],
    'Answer': [sent.text for sent in doc.sents]
}
df = pd.DataFrame(data)
print(f"{len(df)} questions generated.")

# Write result to excel file
output_folder = "./results"
if not os.path.exists(output_folder):    # create “results” folder if not exist
    os.makedirs(output_folder)
filename = replace_accented_chars(keyword)
df.to_excel(f"{output_folder}/{filename}.xlsx")

# Optional file formats
#df.to_csv(f"{output_folder}/{filename}.csv")
#df.to_json(f"{output_folder}/{filename}.json")
</pre>
<p>The post <a href="https://blog.finxter.com/how-to-scrape-wikipedia-pages-to-create-a-question-answer-database/">How to Scrape Wikipedia Pages to Create a Question-Answer Database?</a> appeared first on <a href="https://blog.finxter.com">Be on the Right Side of Change</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>How I Scraped Data From Over 16,000 Gyms from MindBodyOnline.com</title>
		<link>https://blog.finxter.com/how-i-scraped-data-from-over-16000-gyms-from-mindbodyonline-com/</link>
		
		<dc:creator><![CDATA[Charles Blue]]></dc:creator>
		<pubDate>Thu, 19 Oct 2023 08:56:58 +0000</pubDate>
				<category><![CDATA[BeautifulSoup]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Python Requests]]></category>
		<category><![CDATA[Web Scraping]]></category>
		<guid isPermaLink="false">https://blog.finxter.com/?p=1652308</guid>

					<description><![CDATA[<p>This article is based on a freelance job posted on Upwork to scrape data for all the gyms in the USA from MindBodyOnline.com or another similar site. I treated this as a learning project, and it was a good one, as I learned a lot! 🕷️ Web scraping, a technique used to extract data from ... <a title="How I Scraped Data From Over 16,000 Gyms from MindBodyOnline.com" class="read-more" href="https://blog.finxter.com/how-i-scraped-data-from-over-16000-gyms-from-mindbodyonline-com/" aria-label="Read more about How I Scraped Data From Over 16,000 Gyms from MindBodyOnline.com">Read more</a></p>
<p>The post <a href="https://blog.finxter.com/how-i-scraped-data-from-over-16000-gyms-from-mindbodyonline-com/">How I Scraped Data From Over 16,000 Gyms from MindBodyOnline.com</a> appeared first on <a href="https://blog.finxter.com">Be on the Right Side of Change</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">This article is based on a freelance job posted on Upwork to scrape data for all the gyms in the USA from <a href="http://MindBodyOnline.com">MindBodyOnline.com</a> or another similar site. I treated this as a learning project, and it was a good one, as I learned a lot!</p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="598" src="https://blog.finxter.com/wp-content/uploads/2023/10/image-156-1024x598.png" alt="" class="wp-image-1652328" srcset="https://blog.finxter.com/wp-content/uploads/2023/10/image-156-1024x598.png 1024w, https://blog.finxter.com/wp-content/uploads/2023/10/image-156-300x175.png 300w, https://blog.finxter.com/wp-content/uploads/2023/10/image-156-768x449.png 768w, https://blog.finxter.com/wp-content/uploads/2023/10/image-156-1536x897.png 1536w, https://blog.finxter.com/wp-content/uploads/2023/10/image-156.png 1585w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>
</div>


<p class="has-base-2-background-color has-background wp-block-paragraph"><strong><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f577.png" alt="🕷" class="wp-smiley" style="height: 1em; max-height: 1em;" /></strong> <strong>Web scraping</strong>, a technique used to extract data from websites, has become an essential skill on Upwork &#8212; it&#8217;s one of the most sought-after skills on most <a href="https://blog.finxter.com/best-python-freelancer-platforms/">freelancing platforms</a>. Most beginners start with the <strong><a href="https://blog.finxter.com/installing-beautiful-soup/">Beautiful Soup</a></strong> and <strong><a href="https://blog.finxter.com/python-requests-library-2/">Requests</a></strong> modules in Python. While these tools are powerful, they&#8217;re not always sufficient for every site. Enter tools like <strong><a href="https://blog.finxter.com/how-to-open-a-url-in-python-selenium/">Selenium</a></strong>, which, while powerful, can sometimes be overkill or inefficient. </p>



<p class="wp-block-paragraph">So, where should one start? The answer is simple: Always check for an API first.</p>



<h3 class="wp-block-heading">Why Start with APIs?</h3>



<p class="wp-block-paragraph">An <strong>Application Programming Interface (API)</strong> allows two software applications to communicate with each other. Many websites offer APIs to provide structured access to their data, making it easier and more efficient than scraping the web pages directly.</p>



<p class="wp-block-paragraph">Benefits of using APIs:</p>



<ul class="wp-block-list">
<li><strong>Efficiency</strong>: Extracting data from APIs is often faster and less resource-intensive than scraping web pages.</li>



<li><strong>Reliability</strong>: APIs are designed to be accessed programmatically, reducing the chances of breaking changes.</li>



<li><strong>Ethical considerations</strong>: Accessing data via an API is often more in line with a website&#8217;s terms of service than scraping their pages directly.</li>
</ul>



<p class="wp-block-paragraph">MindBodyOnline provides a dedicated API tailored for developers: <a href="https://developers.mindbodyonline.com/ui/documentation/public-api#/http/mindbody-public-api-v6-0/introduction/getting-started">MindBody API</a>. </p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="536" src="https://blog.finxter.com/wp-content/uploads/2023/10/image-140-1024x536.png" alt="" class="wp-image-1652310" srcset="https://blog.finxter.com/wp-content/uploads/2023/10/image-140-1024x536.png 1024w, https://blog.finxter.com/wp-content/uploads/2023/10/image-140-300x157.png 300w, https://blog.finxter.com/wp-content/uploads/2023/10/image-140-768x402.png 768w, https://blog.finxter.com/wp-content/uploads/2023/10/image-140.png 1426w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>
</div>


<p class="wp-block-paragraph">If you&#8217;re aiming to craft an app utilizing their dataset, this API is your ideal resource. It boasts a plethora of endpoints, enabling swift data retrieval and ensuring seamless interaction between your application and their servers.</p>



<p class="wp-block-paragraph"><strong>But what if you aren’t creating an application and just need to scrape data once for research?</strong> MindBodyOnline also retrieves data for its website via an API. Javascript is used to request the data needed to populate their website. We can also make requests for this API.</p>



<h2 class="wp-block-heading">How to check if a website is rendered with Javascript</h2>



<p class="wp-block-paragraph">The site we will be scraping is <a href="https://www.mindbodyonline.com/explore">MindBodyOnline</a>. </p>



<p class="wp-block-paragraph">If a website is rendered with <a href="https://blog.finxter.com/javascript-data-types/">Javascript</a>, we should check the network traffic and see if we can find a request that returns the data we see on the page. This can be done quickly with developer tools. With Chrome, you can bring up developer tools by clicking <code>Ctl-Shift-I</code>. </p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="709" src="https://blog.finxter.com/wp-content/uploads/2023/10/image-143-1024x709.png" alt="" class="wp-image-1652313" srcset="https://blog.finxter.com/wp-content/uploads/2023/10/image-143-1024x709.png 1024w, https://blog.finxter.com/wp-content/uploads/2023/10/image-143-300x208.png 300w, https://blog.finxter.com/wp-content/uploads/2023/10/image-143-768x531.png 768w, https://blog.finxter.com/wp-content/uploads/2023/10/image-143-1536x1063.png 1536w, https://blog.finxter.com/wp-content/uploads/2023/10/image-143.png 1620w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>
</div>


<p class="wp-block-paragraph">From here, we can turn off Javascript, then refresh the page and see if there are any changes. To turn off Javascript, first hit <code>Ctl-Shift-P</code> to bring up the command palette. Start typing Javascript to filter the options, then click “Disable javascript”.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="506" height="117" src="https://blog.finxter.com/wp-content/uploads/2023/10/image-141.png" alt="" class="wp-image-1652311" srcset="https://blog.finxter.com/wp-content/uploads/2023/10/image-141.png 506w, https://blog.finxter.com/wp-content/uploads/2023/10/image-141-300x69.png 300w" sizes="auto, (max-width: 506px) 100vw, 506px" /></figure>
</div>


<p class="wp-block-paragraph">Then refresh the page. As we can see, they use JavaScript for all the data.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="444" height="95" src="https://blog.finxter.com/wp-content/uploads/2023/10/image-142.png" alt="" class="wp-image-1652312" srcset="https://blog.finxter.com/wp-content/uploads/2023/10/image-142.png 444w, https://blog.finxter.com/wp-content/uploads/2023/10/image-142-300x64.png 300w" sizes="auto, (max-width: 444px) 100vw, 444px" /></figure>
</div>


<p class="wp-block-paragraph">Before we can continue, we need to turn JavaScript back on. Bring up the command palette again, filter for javascript, and click “Enable Javascript”. Then refresh the page again.</p>



<h2 class="wp-block-heading">Check the JavaScript Requests</h2>



<p class="wp-block-paragraph">Select the Network tab in developer tools.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="624" height="152" src="https://blog.finxter.com/wp-content/uploads/2023/10/image-144.png" alt="" class="wp-image-1652314" srcset="https://blog.finxter.com/wp-content/uploads/2023/10/image-144.png 624w, https://blog.finxter.com/wp-content/uploads/2023/10/image-144-300x73.png 300w" sizes="auto, (max-width: 624px) 100vw, 624px" /></figure>
</div>


<p class="wp-block-paragraph">Make sure <code>Fetch/XHR</code> and <code>Preserve log</code> are selected. Next, we can click the circle with the line through it to clear the output. Then perform a search to see what requests were performed.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="624" height="192" src="https://blog.finxter.com/wp-content/uploads/2023/10/image-145.png" alt="" class="wp-image-1652315" srcset="https://blog.finxter.com/wp-content/uploads/2023/10/image-145.png 624w, https://blog.finxter.com/wp-content/uploads/2023/10/image-145-300x92.png 300w" sizes="auto, (max-width: 624px) 100vw, 624px" /></figure>
</div>


<p class="wp-block-paragraph">We can then check each item in the output to see if it returns useful information.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="601" height="255" src="https://blog.finxter.com/wp-content/uploads/2023/10/image-146.png" alt="" class="wp-image-1652316" srcset="https://blog.finxter.com/wp-content/uploads/2023/10/image-146.png 601w, https://blog.finxter.com/wp-content/uploads/2023/10/image-146-300x127.png 300w" sizes="auto, (max-width: 601px) 100vw, 601px" /></figure>
</div>


<p class="wp-block-paragraph">We are primarily interested in the response to the request. We are looking for XML data that looks like the data shown on the page. In this case, it is the <code>locations</code> request that contains the data we seek.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="624" height="395" src="https://blog.finxter.com/wp-content/uploads/2023/10/image-147.png" alt="" class="wp-image-1652317" srcset="https://blog.finxter.com/wp-content/uploads/2023/10/image-147.png 624w, https://blog.finxter.com/wp-content/uploads/2023/10/image-147-300x190.png 300w" sizes="auto, (max-width: 624px) 100vw, 624px" /></figure>
</div>


<p class="wp-block-paragraph">We can also see that there is a payload required. When we make our requests, we must provide this payload in the request body. There are three items of interest here. The latitude and longitude allow us to control the city we are pulling data for, and we also need to provide a page number.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="562" height="111" src="https://blog.finxter.com/wp-content/uploads/2023/10/image-148.png" alt="" class="wp-image-1652318" srcset="https://blog.finxter.com/wp-content/uploads/2023/10/image-148.png 562w, https://blog.finxter.com/wp-content/uploads/2023/10/image-148-300x59.png 300w" sizes="auto, (max-width: 562px) 100vw, 562px" /></figure>
</div>


<p class="wp-block-paragraph">MindBody uses pagination, so a relatively small amount of data is pulled with each request. A large city like New York can have over a hundred pages.</p>



<p class="wp-block-paragraph">We go to the headers tab to copy the request URL.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="624" height="120" src="https://blog.finxter.com/wp-content/uploads/2023/10/image-149.png" alt="" class="wp-image-1652319" srcset="https://blog.finxter.com/wp-content/uploads/2023/10/image-149.png 624w, https://blog.finxter.com/wp-content/uploads/2023/10/image-149-300x58.png 300w" sizes="auto, (max-width: 624px) 100vw, 624px" /></figure>
</div>


<h2 class="wp-block-heading">Using Insomnia to Generate Request Headers</h2>



<p class="wp-block-paragraph">From here, we can use a tool to help us with the request syntax. </p>



<p class="has-base-2-background-color has-background wp-block-paragraph"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f517.png" alt="🔗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Insomnia</strong> is a powerful open-source API client tool for testing and debugging APIs. It provides a user-friendly interface to send requests to web services and view responses. With Insomnia, you can define various request types, from simple HTTP GET requests to complex JSON, GraphQL, or even multipart file uploads. You can download the insomnia desktop app <a href="https://insomnia.rest/download">here</a>.</p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="601" src="https://blog.finxter.com/wp-content/uploads/2023/10/image-150-1024x601.png" alt="" class="wp-image-1652320" srcset="https://blog.finxter.com/wp-content/uploads/2023/10/image-150-1024x601.png 1024w, https://blog.finxter.com/wp-content/uploads/2023/10/image-150-300x176.png 300w, https://blog.finxter.com/wp-content/uploads/2023/10/image-150-768x451.png 768w, https://blog.finxter.com/wp-content/uploads/2023/10/image-150.png 1342w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>
</div>


<p class="wp-block-paragraph">Using Insomnia is quite simple. Just paste in the API URL and click <code>Send</code>.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="624" height="152" src="https://blog.finxter.com/wp-content/uploads/2023/10/image-151.png" alt="" class="wp-image-1652321" srcset="https://blog.finxter.com/wp-content/uploads/2023/10/image-151.png 624w, https://blog.finxter.com/wp-content/uploads/2023/10/image-151-300x73.png 300w" sizes="auto, (max-width: 624px) 100vw, 624px" /></figure>
</div>


<p class="wp-block-paragraph">We can check the preview tab to make sure it returns the data we want:</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="493" height="506" src="https://blog.finxter.com/wp-content/uploads/2023/10/image-152.png" alt="" class="wp-image-1652322" srcset="https://blog.finxter.com/wp-content/uploads/2023/10/image-152.png 493w, https://blog.finxter.com/wp-content/uploads/2023/10/image-152-292x300.png 292w" sizes="auto, (max-width: 493px) 100vw, 493px" /></figure>
</div>


<p class="wp-block-paragraph">This is where it gets good. If we click the dropdown on the send button, one of the options is “generate client code”. How convenient! Just click Python as the language and use the Requests module and you can click “Copy to Clipboard” and you’re off to the races.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="624" height="397" src="https://blog.finxter.com/wp-content/uploads/2023/10/image-153.png" alt="" class="wp-image-1652323" srcset="https://blog.finxter.com/wp-content/uploads/2023/10/image-153.png 624w, https://blog.finxter.com/wp-content/uploads/2023/10/image-153-300x191.png 300w" sizes="auto, (max-width: 624px) 100vw, 624px" /></figure>
</div>


<h2 class="wp-block-heading">A Simple Scrapy Spider</h2>



<p class="wp-block-paragraph">The code can be found on <a href="https://github.com/PythonCB/Scrape_MindBodyOnline">Github</a>. I will walk through the code below, starting with the imports.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import scrapy
import json
import pandas as pd
from scrapy.crawler import CrawlerProcess
import os
</pre>



<p class="wp-block-paragraph"><a href="https://blog.finxter.com/python-scrapy-scraping-dynamic-website-with-api-generated-content/">Scrapy</a> is a good option because it can handle multiple requests at the same time with <a href="https://blog.finxter.com/python-async-for-mastering-asynchronous-iteration-in-python/">asynchronous</a> processing. Scapy has a lot of bells and whistles and a fair bit of a learning curve, but it’s also possible to avoid a lot of the extra complexity. The goal here was to place all the code in one simple script.</p>



<p class="wp-block-paragraph">First, we have to create a spider class. The class is pretty large so I’ll display it in chunks.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">class MindbodySpider(scrapy.Spider):
    name = 'mindbody_spider'

    custom_settings = {
        'CONCURRENT_REQUESTS': 5,
        'DOWNLOAD_DELAY': 3.2,
    }
</pre>



<p class="wp-block-paragraph">Our class inherits from one of the Scrapy <code>Spider</code> classes with <code>scrapy.Spider</code> being the simplest. In the custom settings, with <code>CONCURRENT_REQUESTS</code> set to <code>5</code>, scrapy will be processing six requests at a time, starting a new one as soon as one finishes. </p>



<p class="wp-block-paragraph">We use a <code>DOWNLOAD_DELAY</code> so we don’t bombard the website with too many requests at once.</p>



<p class="wp-block-paragraph">Next, we need a starting template for the payload</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">starting_payload = '''{
                          "sort":"-_score,distance",
                          "page":{"size":50,"number":&lt;&lt;num>>},
                          "filter":{"categories":"any",
                                    "latitude":&lt;&lt;lat>>,
                                    "longitude":&lt;&lt;lon>>,
                                    "categoryTypes":"any"}
                       }'''
</pre>



<p class="wp-block-paragraph">Next, we have the headers that Insomnia so helpfully provided for us.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">headers = {
        "cookie": "__cf_bm=zdIhLHXKd2OAveBChKORUMdydUFVzC2Ma51sQxv.UJ0-1694646164-0-Abmbwcj2wNw%2FpityY4DWRWy%2FftBkjTO0vQ3tZ0gwU0P5bsTqcasf2XZlBwL%2BUaevGaH%2BTDzZOJPBXbWYwgsXkJc%3D",
        "authority": "prod-mkt-gateway.mindbody.io",
        "accept": "application/vnd.api+json",
        "accept-language": "en-US,en;q=0.9",
        "content-type": "application/json",
        "origin": "https://www.mindbodyonline.com",
        "sec-ch-ua": "^\^Not/A",
        "sec-ch-ua-mobile": "?0",
        "sec-ch-ua-platform": "^\^Windows^^",
        "sec-fetch-dest": "empty",
        "sec-fetch-mode": "cors",
        "sec-fetch-site": "cross-site",
        "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36",
        "x-mb-app-build": "2023-08-02T13:33:44.200Z",
        "x-mb-app-name": "mindbody.io",
        "x-mb-app-version": "e5d1fad6",
        "x-mb-user-session-id": "oeu1688920580338r0.2065068094427127"
    }
</pre>



<p class="wp-block-paragraph">Then a very simple <code>init</code> method</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">def __init__(self):
        scrapy.Spider.__init__(self)
        self.city_count = 0
</pre>



<p class="wp-block-paragraph">The <code>start_requests</code> method loops through each city. This is the main loop that creates the first request for each city.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">def start_requests(self):
        cities = pd.read_csv('uscities.csv')

        for idx, city in cities[].iterrows():
            lat, lon = city.lat, city.lng
            self.logger.info(f"{city.city}, {city.state_id} started")

            # Start with the first page for each city
            payload = self.starting_payload.replace('&lt;&lt;pg>>', '1').replace('&lt;&lt;lat>>', str(lat)).replace('&lt;&lt;lon>>', str(lon))

            yield scrapy.Request(
                url="https://prod-mkt-gateway.mindbody.io/v1/search/locations",
                method="GET",
                body=payload,
                headers=self.headers,
                meta={'city_name': city.city, 'page_num': 1, 'lat': lat, 'lon': lon, 'state': city.state_id},
                callback=self.parse
            )
</pre>



<p class="wp-block-paragraph">The code is pretty simple. We <a href="https://blog.finxter.com/how-to-create-a-dataframe-in-pandas/">create a DataFrame</a> from a <a href="https://blog.finxter.com/read-a-csv-file-to-a-pandas-dataframe/">CSV file</a> with city information and then loop through it with the <code>iterrows</code> method. We create the payload for the request using the template and the lat/long values from the DataFrame. The page is set to 1 each time. We will handle additional pages later.</p>



<p class="wp-block-paragraph">Finally, we yield a <code>scrapy.Request</code> object. We use <code><a href="https://blog.finxter.com/yield-keyword-in-python-a-simple-illustrated-guide/">yield</a></code> instead of <code><a href="https://blog.finxter.com/python-return/">return</a></code> so we can handle <a href="https://blog.finxter.com/python-async-requests-getting-urls-concurrently-via-https/">multiple requests concurrently</a>. The body is our modified payload, and we use the same header for each request.</p>



<p class="wp-block-paragraph">What do we do with the response returned from the request? As soon as the response is returned it is fed into the parse method thanks to the callback parameter:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">callback=self.parse</pre>



<p class="wp-block-paragraph">The <code>meta</code> parameter gives us a way to pass information to the <code>callback</code> function. We need the page <code>num</code>, <code>lat</code>, <code>lon</code> values for the next request.  <code>City_name</code> and <code>state</code> are used for screen outputs.</p>



<p class="wp-block-paragraph">The list of cities was downloaded off the web. Many different options will work, as long as they contain latitude and longitude values.</p>



<h2 class="wp-block-heading">Parsing the Response</h2>



<p class="wp-block-paragraph">The <code>parse</code> method is a little long, but not too complicated. </p>



<p class="wp-block-paragraph">Getting the data and saving it is very easy. We just convert <code>response.text</code> to a DataFrame and <a href="https://blog.finxter.com/how-to-export-pandas-dataframe-to-csv-example/">save it to a CSV file</a>. If the file already exists, we will append the data and not include a header. Otherwise, we create a new CSV file and include a header.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">def parse(self, response):
        data = json.loads(response.text)
        gyms_df = pd.json_normalize(data['data'])

        # Save the dataframe to a CSV
        city_name = response.meta['city_name']
        state = response.meta['state']
        fname = f'{city_name} {state}.csv'.replace(' ', '_')
        csv_path = f'./data/cities2/{fname}'

        # Check if file exists to determine the write mode
        write_mode = 'a' if os.path.exists(csv_path) else 'w'

        gyms_df.to_csv(csv_path, 
                       mode=write_mode, 
                       index=False, 
                       header=(not os.path.exists(csv_path)))         
</pre>



<h2 class="wp-block-heading">Handling Pagination</h2>



<p class="wp-block-paragraph">To move on to the next page, we need to create another Scrapy Request. For the payload we use the same latitude and longitude and increment the page number by 1.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">        # Check if there's another page and if so, initiate the request
        next_page_num = response.meta['page_num'] + 1
        if next_page_num &lt;= 150:  # Optional: upper limit
            lat, lon = response.meta['lat'], response.meta['lon']  # Assuming you store lat and lon in meta too

            payload = self.starting_payload.replace('&lt;&lt;pg>>', '1').replace('&lt;&lt;lat>>', str(lat)).replace('&lt;&lt;lon>>', str(lon))
</pre>



<h2 class="wp-block-heading">Make the Request for the Next Page</h2>



<p class="wp-block-paragraph">To finish the <code>parse</code> method, all we have to do is make another request with the new payload.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">yield scrapy.Request(
                url="https://prod-mkt-gateway.mindbody.io/v1/search/locations",
                method="GET",
                body=payload,
                headers=self.headers,
                meta={'city_name': response.meta['city_name'], 
                      'page_num': next_page_num, 
                      'lat': lat, 
                      'lon': lon,
                      'state': state},
                callback=self.parse
            )

        self.city_count += 1
        print(response.meta['city_name'], f'complete ({self.city_count})')
        self.logger.info(f"""{response.meta['city_name']}, 
                           {response.meta['state']} is complete""")
</pre>



<h2 class="wp-block-heading">How the Pagination Loop Terminates</h2>



<p class="wp-block-paragraph">What happens if there are 100 pages for the current city and the code sends a request with <code>page_num = 101</code>? </p>



<p class="wp-block-paragraph">The request will not return anything, so the callback function won’t get called and the recursive loop for that city will stop. </p>



<p class="wp-block-paragraph">Then the <code>start_requests</code> loop will move on to the next city.</p>



<h2 class="wp-block-heading">It’s alive! Setting Our Little Spider Loose</h2>



<p class="wp-block-paragraph">To get our creepy critter crawling, we create a <code>CrawlerProcess</code>. Then tell it to crawl. Then tell it to start. On your mark, get set, CRAWL!</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">process = CrawlerProcess()
process.crawl(MindbodySpider)
process.start()
</pre>



<h2 class="wp-block-heading">Results</h2>



<p class="wp-block-paragraph">I was able to scrape data for 16,000 cities in about half a week. I think I averaged about 100 cities an hour. The larger cities had over a hundred pages but there were <strong>thousands upon thousands of cities with 5-10 pages</strong>.</p>



<p class="wp-block-paragraph">What about the data? It’s fairly extensive and could be very useful.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="518" height="788" src="https://blog.finxter.com/wp-content/uploads/2023/10/image-154.png" alt="" class="wp-image-1652324" srcset="https://blog.finxter.com/wp-content/uploads/2023/10/image-154.png 518w, https://blog.finxter.com/wp-content/uploads/2023/10/image-154-197x300.png 197w" sizes="auto, (max-width: 518px) 100vw, 518px" /></figure>
</div>


<p class="wp-block-paragraph">Pretty good information related to services offered, location, amenities, total ratings etc. Looking at the rest of the columns:</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="510" height="396" src="https://blog.finxter.com/wp-content/uploads/2023/10/image-155.png" alt="" class="wp-image-1652325" srcset="https://blog.finxter.com/wp-content/uploads/2023/10/image-155.png 510w, https://blog.finxter.com/wp-content/uploads/2023/10/image-155-300x233.png 300w" sizes="auto, (max-width: 510px) 100vw, 510px" /></figure>
</div>


<h2 class="wp-block-heading">Conclusion</h2>



<p class="wp-block-paragraph">Uncovering the API proved invaluable. It eliminated the need to craft path selectors for individual data elements, significantly streamlining the process. Moreover, it spared me from devising a Scrapy workaround for the JavaScript-rendered page. Investing time in learning Scrapy was a sound decision, given its superior speed compared to other methods I explored.</p>



<p class="wp-block-paragraph">Looking ahead, the logical progression is to integrate the data into platforms like Jupyter Notebook, Power BI, or Tableau. Furthermore, storing the data in a database seems apt, especially considering the apparent one-to-many relationships observed in each city, like categories and subcategories.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">If you want to become a master web scraper, feel free to check out our academy course with downloadable PDF certificate to showcase your skills to future employers or freelancing clients:</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="800" height="341" src="https://blog.finxter.com/wp-content/uploads/2023/10/image-157.png" alt="" class="wp-image-1652329" srcset="https://blog.finxter.com/wp-content/uploads/2023/10/image-157.png 800w, https://blog.finxter.com/wp-content/uploads/2023/10/image-157-300x128.png 300w, https://blog.finxter.com/wp-content/uploads/2023/10/image-157-768x327.png 768w" sizes="auto, (max-width: 800px) 100vw, 800px" /></figure>
</div>


<p class="has-base-2-background-color has-background wp-block-paragraph"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f517.png" alt="🔗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Academy</strong>: <a href="https://academy.finxter.com/university/web-scraping-with-beautifulsoup/">Web Scraping with BeautifulSoup</a></p>
<p>The post <a href="https://blog.finxter.com/how-i-scraped-data-from-over-16000-gyms-from-mindbodyonline-com/">How I Scraped Data From Over 16,000 Gyms from MindBodyOnline.com</a> appeared first on <a href="https://blog.finxter.com">Be on the Right Side of Change</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Get First N Google Search Results in Python (Official Way)</title>
		<link>https://blog.finxter.com/get-first-n-google-search-results-in-python-official-way/</link>
		
		<dc:creator><![CDATA[Chris]]></dc:creator>
		<pubDate>Sun, 08 Oct 2023 16:24:14 +0000</pubDate>
				<category><![CDATA[Automation]]></category>
		<category><![CDATA[Productivity]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Web Scraping]]></category>
		<guid isPermaLink="false">https://blog.finxter.com/?p=1652078</guid>

					<description><![CDATA[<p>In this article, I&#8217;ll share my method of retrieving the first n Google search results for a given keyword and extract the text content from the resulting URLs. The search_google(query, num_results) function performs a search using the Google Custom Search JSON API, with the search query and the desired number of results (num_results) as parameters. ... <a title="Get First N Google Search Results in Python (Official Way)" class="read-more" href="https://blog.finxter.com/get-first-n-google-search-results-in-python-official-way/" aria-label="Read more about Get First N Google Search Results in Python (Official Way)">Read more</a></p>
<p>The post <a href="https://blog.finxter.com/get-first-n-google-search-results-in-python-official-way/">Get First N Google Search Results in Python (Official Way)</a> appeared first on <a href="https://blog.finxter.com">Be on the Right Side of Change</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">In this article, I&#8217;ll share my method of retrieving the first <code>n</code> Google search results for a given keyword and extract the text content from the resulting URLs. </p>



<p class="has-global-color-8-background-color has-background wp-block-paragraph">The <code>search_google(query, num_results)</code> function performs a search using the <strong>Google Custom Search JSON API</strong>, with the search query and the desired number of results (<code>num_results</code>) as parameters. </p>



<p class="wp-block-paragraph">Upon a successful API request (<code>HTTP Status Code 200</code>), the function extracts and prints titles, URLs, and snippets of the search results. </p>



<p class="wp-block-paragraph">Subsequently, it employs the <code>get_texts_from_urls</code> function to retrieve the text content from each URL, which internally uses <code>get_text_from_url</code> to send a GET request to each URL, <a href="https://blog.finxter.com/how-to-extract-html-h1-h2-h3-headlines-from-a-python-string-using-regex/">parse the HTML using BeautifulSoup</a>, and extract the text. The extracted texts are then printed along with their respective lengths. </p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="683" src="https://blog.finxter.com/wp-content/uploads/2023/10/pexels-photo-14706188-1024x683.webp" alt="" class="wp-image-1652082" srcset="https://blog.finxter.com/wp-content/uploads/2023/10/pexels-photo-14706188-1024x683.webp 1024w, https://blog.finxter.com/wp-content/uploads/2023/10/pexels-photo-14706188-300x200.webp 300w, https://blog.finxter.com/wp-content/uploads/2023/10/pexels-photo-14706188-768x512.webp 768w, https://blog.finxter.com/wp-content/uploads/2023/10/pexels-photo-14706188.webp 1125w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>
</div>


<p class="wp-block-paragraph">The API key (<code>google_search_api</code>) and Custom Search Engine ID (<code>google_search_cx</code>) are needed to authenticate and specify the search engine, respectively. Both are free though. <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f609.png" alt="😉" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p class="has-base-2-background-color has-background wp-block-paragraph"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f9d1-200d-1f4bb.png" alt="🧑‍💻" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Note</strong>: The API key and Custom Search Engine ID should be obtained from <a href="https://developers.google.com/custom-search/v1/introduction">Google Cloud Console</a> and <a href="https://programmablesearchengine.google.com/controlpanel">Programmable Search Engine</a>, respectively, and should be kept confidential to prevent unauthorized usage. Both are free for up to 100 requests per hour or so.</p>



<p class="wp-block-paragraph">Otherwise, you can just copy and paste the following code into your Jupyter notebook (or Google Colab):</p>



<pre class="EnlighterJSRAW" data-enlighter-language="python" data-enlighter-theme="" data-enlighter-highlight="5-7" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import requests
from bs4 import BeautifulSoup


keyword = 'get stock price in excel cell'
google_search_api = '...' # https://developers.google.com/custom-search/v1/introduction
google_search_cx = '...' # https://programmablesearchengine.google.com/controlpanel/create


def search_google(query, num_results=10):
    '''Returns a list of strings with the text of the first n google results'''

    # Google Custom Search JSON API URL
    api_url = "https://www.googleapis.com/customsearch/v1"
    
    # Parameters for the API request
    params = {
        'key': google_search_api,  # Your API key
        'cx': google_search_cx,  # Your Custom Search Engine ID
        'q': query,  # The search query
        'num': num_results  # The number of results to retrieve
    }
    
    # Make the API request
    response = requests.get(api_url, params=params)
    
    # Check if the request was successful (HTTP Status Code 200)
    if response.status_code == 200:
        # Parse the JSON response
        results = response.json()
        
        titles = [item['title'] for index, item in enumerate(results.get('items', []))]
        print(titles)

        urls = [item['link'] for index, item in enumerate(results.get('items', []))]
        print(urls)

        snippets = [item['snippet'] for index, item in enumerate(results.get('items', []))]
        print(snippets)

        # Get the Text of all URLs
        texts = get_texts_from_urls(urls)
        print(texts)

        for text in texts:
            print(len(text))

        

    else:
        print("Failed to retrieve results:", response.status_code, response.text)


def get_text_from_url(url):
    try:
        # Send a GET request to the URL
        response = requests.get(url)
        
        # Check if the request was successful (HTTP Status Code 200)
        if response.status_code == 200:
            # Parse the HTML content of the page with BeautifulSoup
            soup = BeautifulSoup(response.text, 'html.parser')
            
            # Extract text from the parsed HTML
            return soup.get_text(separator="\n", strip=True)
        else:
            print(f"Failed to retrieve content from {url}. Status code: {response.status_code}")
            return None
    except Exception as e:
        print(f"An error occurred while fetching {url}: {str(e)}")
        return None


def get_texts_from_urls(urls):
    texts = []
    for url in urls:
        text = get_text_from_url(url)
        if text:
            texts.append(text)
    return texts



search_google(keyword, 5)
</pre>



<p class="wp-block-paragraph">If you don&#8217;t use Google Colab Notebook to run the code (e.g., in your own <code>.py</code> script), you may need to <a href="https://blog.finxter.com/how-to-install-beautifulsoup4-in-python/">install BeautifulSoup</a> and <a href="https://blog.finxter.com/how-to-install-requests-in-python/">Requests</a> &#8212; check out the linked articles for a quick tutorial on accomplishing this.</p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="885" src="https://blog.finxter.com/wp-content/uploads/2023/10/image-48-1024x885.png" alt="" class="wp-image-1652079" srcset="https://blog.finxter.com/wp-content/uploads/2023/10/image-48-1024x885.png 1024w, https://blog.finxter.com/wp-content/uploads/2023/10/image-48-300x259.png 300w, https://blog.finxter.com/wp-content/uploads/2023/10/image-48-768x664.png 768w, https://blog.finxter.com/wp-content/uploads/2023/10/image-48.png 1268w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>
</div>


<p class="wp-block-paragraph">For the provided keyword <code>'get stock price in excel cell'</code>, the output of this code was as follows:</p>



<pre class="wp-block-preformatted"><code>['Get a stock quote - Microsoft Support', 'The formula that returns real-time stock prices', 'Excel data types: Stocks and geography - Microsoft Support', 'Add Real-Time Stock Prices And Metrics To Excel', 'Get stock price (latest close) - Excel formula | Exceljet']
['https://support.microsoft.com/en-us/office/get-a-stock-quote-e5af3212-e024-4d4c-bea0-623cf07fbc54', 'https://www.morningbrew.com/money-scoop/stories/2021/08/24/formula-returns-realtime-stock-prices', 'https://support.microsoft.com/en-us/office/excel-data-types-stocks-and-geography-61a33056-9935-484f-8ac8-f1a89e210877', 'https://www.thespreadsheetguru.com/add-real-time-stock-prices-and-metrics-to-excel/', 'https://exceljet.net/formulas/get-stock-price-latest-close']
["Type some text in cells. · Then select the cells. · Although it's not required, we recommend creating an Excel table. · With the cells still selected, go to the\xa0...", 'Aug 24, 2021 ... In Excel: Yep, Excel can return stock prices, too. Enter each ticker in its own cell > Highlight the cells > Select “Data” > “Stocks” > Tap\xa0...', '... price are getting extracted from the Geography data type in column A. Type some text in cells. If you want stock information, type a ticker symbol, company\xa0...', 'Jul 28, 2019 ... If you click the Convert to </code>

<code>...</code>

<code>problem with this page\nHelp us improve Exceljet\nYou must have JavaScript enabled to use this form.\nName\nEmail\nProblem\n*\nYour email address is private and not shared.\nExceljet\nQuick, clean, and to the point\nResources\nTraining\nVideos\nFunctions\nFormulas\nShortcuts\nArticles\nAbout Us\nAbout\nTestimonials\nTopics\nContact\nNewsletter Sign-up\nWork faster in Excel.\nJoin more than 100,000\npeople who get weekly tips from us.\nEmail\nHP\n© 2012-2023 Exceljet.\nTerms of use\nVisit our Twitter page']
6189
9885
8960</code></pre>



<p class="wp-block-paragraph">Thanks for reading the article and feel free to join our 100% free email academy by downloading our Python cheat sheets:</p>



<p>The post <a href="https://blog.finxter.com/get-first-n-google-search-results-in-python-official-way/">Get First N Google Search Results in Python (Official Way)</a> appeared first on <a href="https://blog.finxter.com">Be on the Right Side of Change</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Regex to Extract All Email Addresses from a String ✅ (Tutorial + Online Tool)</title>
		<link>https://blog.finxter.com/regex-to-extract-all-email-addresses-from-a-string-%e2%9c%85-tutorial-online-tool/</link>
		
		<dc:creator><![CDATA[Chris]]></dc:creator>
		<pubDate>Fri, 06 Oct 2023 21:05:51 +0000</pubDate>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Python String]]></category>
		<category><![CDATA[Regex]]></category>
		<category><![CDATA[Web Scraping]]></category>
		<guid isPermaLink="false">https://blog.finxter.com/?p=1652012</guid>

					<description><![CDATA[<p>I have just created this small helper tool to extract all email addresses from a huge text file or string. Simply copy and paste your text into the field and click &#8220;Extract Emails&#8220;: 👇 Email Extractor Enter Text: Extract Emails Copy to Clipboard Extracted Emails: JavaScript Regex In the online tool above, I used a ... <a title="Regex to Extract All Email Addresses from a String ✅ (Tutorial + Online Tool)" class="read-more" href="https://blog.finxter.com/regex-to-extract-all-email-addresses-from-a-string-%e2%9c%85-tutorial-online-tool/" aria-label="Read more about Regex to Extract All Email Addresses from a String ✅ (Tutorial + Online Tool)">Read more</a></p>
<p>The post <a href="https://blog.finxter.com/regex-to-extract-all-email-addresses-from-a-string-%e2%9c%85-tutorial-online-tool/">Regex to Extract All Email Addresses from a String ✅ (Tutorial + Online Tool)</a> appeared first on <a href="https://blog.finxter.com">Be on the Right Side of Change</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">I have just created this small helper tool to extract all email addresses from a huge text file or string. Simply copy and paste your text into the field and click &#8220;<code>Extract Emails</code>&#8220;: <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f447.png" alt="👇" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<div id="emailExtractorApp">
    <h2>Email Extractor</h2>
    <label for="inputText">Enter Text:</label>
    <textarea id="inputText" rows="4" cols="50"></textarea>
    <button onclick="extractEmails()">Extract Emails</button>
    <button onclick="copyToClipboard()" id="copyButton" disabled>Copy to Clipboard</button>
    <h3>Extracted Emails:</h3>
    <p id="outputEmails"></p>
    <style>
        #emailExtractorApp {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            margin: 20px;
            padding: 20px;
            border: 2px solid #4A90E2;
            border-radius: 10px;
            max-width: 600px;
            background-color: #F7F8FA;
        }

        textarea {
            width: calc(100% - 20px);
            margin-bottom: 15px;
            padding: 10px;
            border-radius: 5px;
            border: 1px solid #B4B4B4;
            font-size: 16px;
        }

        button {
            background-color: #4A90E2;
            color: white;
            padding: 12px 25px;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            margin-bottom: 15px;
            font-size: 16px;
            margin-right: 10px;
        }

        button:hover {
            background-color: #3180B8;
        }

        h2, h3 {
            color: #333;
            margin-bottom: 10px;
        }

        p {
            color: #666;
            font-size: 16px;
            white-space: pre-line; /* Keeps the line breaks */
        }

        label {
            font-size: 16px;
            color: #333;
            margin-bottom: 5px;
            display: block;
        }
    </style>
    <script>
        function extractEmails() {
            // Get the input text
            var inputText = document.getElementById('inputText').value;
            
            // Regular expression to identify email addresses
            var emailRegex = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
            
            // Extract email addresses
            var extractedEmails = inputText.match(emailRegex);
            
            // Display the extracted emails
            document.getElementById('outputEmails').innerText = extractedEmails ? extractedEmails.join('\n') : 'No emails found';
            
            // Enable the copy button if emails are found
            document.getElementById('copyButton').disabled = !extractedEmails;
        }

        function copyToClipboard() {
            // Create a temporary textarea to copy from
            var tempTextarea = document.createElement('textarea');
            tempTextarea.value = document.getElementById('outputEmails').innerText;
            document.body.appendChild(tempTextarea);
            tempTextarea.select();
            document.execCommand('copy');
            document.body.removeChild(tempTextarea);
            alert('Email addresses copied to clipboard!');
        }
    </script>
</div>




<h2 class="wp-block-heading">JavaScript Regex</h2>



<p class="wp-block-paragraph">In the online tool above, I used a JavaScript regular expression:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">var emailRegex = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g; </pre>



<p class="wp-block-paragraph">The variable <code>emailRegex</code> holds a regular expression, which is a pattern used to match character combinations in strings, specifically designed here to identify email addresses. </p>



<p class="wp-block-paragraph">In the pattern <code>/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g</code>, <code>[a-zA-Z0-9._%+-]+</code> matches the username part of the email, allowing for a combination of uppercase and lowercase letters, numbers, and several special characters. </p>



<p class="wp-block-paragraph">The <code>@</code> symbol is a literal match, ensuring the presence of the at-symbol in the email. </p>



<p class="wp-block-paragraph"><code>[a-zA-Z0-9.-]+</code> matches the domain name, allowing letters, numbers, periods, and hyphens. <code>\.</code> ensures a literal period is present, and <code>[a-zA-Z]{2,}</code> matches the top-level domain (like com, org, net), ensuring it is composed of at least two letters. </p>



<p class="wp-block-paragraph">The <code>g</code> at the end of the pattern indicates a global search, meaning it will match all instances of the pattern in the provided string, not just the first one it encounters. </p>



<p class="wp-block-paragraph">This regular expression provides a general pattern for identifying email addresses within a text, although it&#8217;s worth noting that validating email addresses using a regular expression can get exceedingly complex due to the wide variety of valid formats specified by the standard.</p>



<h2 class="wp-block-heading">Regex to Match Email Addresses in Different Programming Languages</h2>



<p class="wp-block-paragraph">Fortunately, regular expressions (regex) tend to be quite consistent across different programming languages and environments, as many languages implement regular expressions in a way that adheres to a standard (often, the Perl 5 regular expression syntax). However, there can be slight variations or additional features in some languages. </p>



<p class="wp-block-paragraph">Below is a general regex pattern for matching email addresses, along with examples of how it might be implemented in various programming languages:</p>



<p class="wp-block-paragraph"><strong>General Regex Pattern:</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}</pre>



<h4 class="wp-block-heading">1. JavaScript:</h4>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">var emailRegex = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;</pre>



<h4 class="wp-block-heading">2. Python (using <code>re</code> module):</h4>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import re
email_regex = re.compile(r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}')</pre>



<p class="wp-block-paragraph">You can also check out <a href="https://blog.finxter.com/how-to-extract-emails-from-any-website-using-python/">this Finxter article</a> to learn more about scraping websites to extract email addresses. However, make sure you have the right to do so!</p>



<h4 class="wp-block-heading">3. Java:</h4>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">Pattern emailRegex = Pattern.compile("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}");</pre>



<h4 class="wp-block-heading">4. C# (.NET):</h4>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">Regex emailRegex = new Regex(@"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}");</pre>



<h4 class="wp-block-heading">5. PHP (using <code>preg_match</code>):</h4>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">$emailRegex = '/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/';</pre>



<h4 class="wp-block-heading">6. Ruby:</h4>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">email_regex = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/</pre>



<h4 class="wp-block-heading">7. Swift:</h4>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">let emailRegex = "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}"</pre>



<h4 class="wp-block-heading">8. Kotlin:</h4>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">val emailRegex = "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}".toRegex()</pre>



<h4 class="wp-block-heading">9. Perl:</h4>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">my $email_regex = qr/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/;</pre>



<h4 class="wp-block-heading">10. R:</h4>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">email_regex &lt;- "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}"</pre>



<p class="wp-block-paragraph">Note that the above regex pattern is quite basic and will cover many, but not all, valid email address formats. Email validation can get quite complex if you want to cover all valid email formats as specified by the standards (RFC 5322, RFC 6531). The above examples should work for many common use cases, but be sure to test thoroughly with your own data to ensure no valid email addresses are being missed.</p>



<p class="wp-block-paragraph">Have fun, thanks for reading, and keep learning! <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f680.png" alt="🚀" class="wp-smiley" style="height: 1em; max-height: 1em;" /> </p>



<p class="wp-block-paragraph">Feel free to join our <a href="https://blog.finxter.com/email-academy/">free email newsletter</a> to stay tuned about exciting tech news while learning coding and AI tools, as well as prompt engineering:</p>



<p>The post <a href="https://blog.finxter.com/regex-to-extract-all-email-addresses-from-a-string-%e2%9c%85-tutorial-online-tool/">Regex to Extract All Email Addresses from a String ✅ (Tutorial + Online Tool)</a> appeared first on <a href="https://blog.finxter.com">Be on the Right Side of Change</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Python BS4 &#8211; How to Scrape Absolute URL Instead of Relative Path</title>
		<link>https://blog.finxter.com/scraping-the-absolute-url-of-instead-of-the-relative-path-using-beautifulsoup/</link>
		
		<dc:creator><![CDATA[Shubham Sayon]]></dc:creator>
		<pubDate>Thu, 28 Sep 2023 19:56:58 +0000</pubDate>
				<category><![CDATA[BeautifulSoup]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Web Scraping]]></category>
		<guid isPermaLink="false">https://blog.finxter.com/?p=22845</guid>

					<description><![CDATA[<p>Summary: Use urllib.parse.urljoin() to scrape the base URL and the relative path and join them to extract the complete/absolute URL. You can also concatenate the base URL and the absolute path to derive the absolute path; but make sure to take care of erroneous situations like extra forward-slash in this case. Quick Answer When web ... <a title="Python BS4 &#8211; How to Scrape Absolute URL Instead of Relative Path" class="read-more" href="https://blog.finxter.com/scraping-the-absolute-url-of-instead-of-the-relative-path-using-beautifulsoup/" aria-label="Read more about Python BS4 &#8211; How to Scrape Absolute URL Instead of Relative Path">Read more</a></p>
<p>The post <a href="https://blog.finxter.com/scraping-the-absolute-url-of-instead-of-the-relative-path-using-beautifulsoup/">Python BS4 &#8211; How to Scrape Absolute URL Instead of Relative Path</a> appeared first on <a href="https://blog.finxter.com">Be on the Right Side of Change</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="has-global-color-8-background-color has-background wp-block-paragraph"><strong>Summary: </strong>Use <a href="https://docs.python.org/3/library/urllib.parse.html#urllib.parse.urljoin" target="_blank" rel="noreferrer noopener"><code data-enlighter-language="generic" class="EnlighterJSRAW">urllib.parse.urljoin()</code></a> to scrape the base URL and the relative path and join them to extract the complete/<strong>absolute </strong>URL. You can also concatenate the base URL and the absolute path to derive the absolute path; but make sure to take care of erroneous situations like extra forward-slash in this case.</p>



<h2 class="wp-block-heading">Quick Answer</h2>



<p class="wp-block-paragraph">When web scraping with BeautifulSoup in Python, you may encounter relative URLs (e.g., <code>/page2.html</code>) instead of absolute URLs (e.g., <code>http://example.com/page2.html</code>). To convert relative URLs to absolute URLs, you can use the <code>urljoin()</code> function from the <code>urllib.parse</code> module.</p>



<p class="wp-block-paragraph">Below is an example of how to extract absolute URLs from the <code>a</code> tags on a webpage using <code>BeautifulSoup</code> and <code>urljoin</code>:</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="816" height="757" src="https://blog.finxter.com/wp-content/uploads/2023/09/image-130.png" alt="" class="wp-image-1651860" srcset="https://blog.finxter.com/wp-content/uploads/2023/09/image-130.png 816w, https://blog.finxter.com/wp-content/uploads/2023/09/image-130-300x278.png 300w, https://blog.finxter.com/wp-content/uploads/2023/09/image-130-768x712.png 768w" sizes="auto, (max-width: 816px) 100vw, 816px" /></figure>
</div>


<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">from bs4 import BeautifulSoup
import requests
from urllib.parse import urljoin

# URL of the webpage you want to scrape
url = 'http://example.com'

# Send an HTTP request to the URL
response = requests.get(url)
response.raise_for_status()  # Raise an error for bad responses

# Parse the webpage content
soup = BeautifulSoup(response.text, 'html.parser')

# Find all the 'a' tags on the webpage
for a_tag in soup.find_all('a'):
    # Get the href attribute from the 'a' tag
    href = a_tag.get('href')

    # Use urljoin to convert the relative URL to an absolute URL
    absolute_url = urljoin(url, href)

    # Print the absolute URL
    print(absolute_url)</pre>



<p class="wp-block-paragraph">In this example:</p>



<ul class="wp-block-list">
<li><code>url</code> is the URL of the webpage you want to scrape.</li>



<li><code>response</code> is the HTTP response obtained by sending an HTTP GET request to the URL.</li>



<li><code>soup</code> is a <code>BeautifulSoup</code> object that contains the parsed HTML content of the webpage.</li>



<li><code>soup.find_all('a')</code> finds all the <code>a</code> tags on the webpage.</li>



<li><code>a_tag.get('href')</code> gets the <code>href</code> attribute from an <code>a</code> tag, which is the relative URL.</li>



<li><code>urljoin(url, href)</code> converts the relative URL to an absolute URL by joining it with the base URL.</li>



<li><code>absolute_url</code> is the absolute URL, which is printed to the console.</li>
</ul>



<p class="wp-block-paragraph">Now that you have a quick overview let&#8217;s dive into the specific problem more deeply and discuss various methods to solve this easily and effectively. <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f447.png" alt="👇" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<h2 class="wp-block-heading">Problem Formulation</h2>



<p class="wp-block-paragraph"><strong>Problem: </strong>How do you extract all the absolute URLs from an HTML page?</p>



<p class="wp-block-paragraph"><strong>Example: </strong>Consider the following webpage which has numerous links:</p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="355" src="https://blog.finxter.com/wp-content/uploads/2023/09/image-129-1024x355.png" alt="" class="wp-image-1651858" srcset="https://blog.finxter.com/wp-content/uploads/2023/09/image-129-1024x355.png 1024w, https://blog.finxter.com/wp-content/uploads/2023/09/image-129-300x104.png 300w, https://blog.finxter.com/wp-content/uploads/2023/09/image-129-768x266.png 768w, https://blog.finxter.com/wp-content/uploads/2023/09/image-129.png 1266w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>
</div>

<div class="wp-block-image">
<figure class="aligncenter size-large is-resized"><img loading="lazy" decoding="async" src="https://blog.finxter.com/wp-content/uploads/2021/02/relative_links-1024x319.png" alt="" class="wp-image-22847" style="object-fit:contain;width:784px;height:243px" width="784" height="243" srcset="https://blog.finxter.com/wp-content/uploads/2021/02/relative_links-1024x319.png 1024w, https://blog.finxter.com/wp-content/uploads/2021/02/relative_links-300x93.png 300w, https://blog.finxter.com/wp-content/uploads/2021/02/relative_links-768x239.png 768w, https://blog.finxter.com/wp-content/uploads/2021/02/relative_links-150x47.png 150w, https://blog.finxter.com/wp-content/uploads/2021/02/relative_links.png 1305w" sizes="auto, (max-width: 784px) 100vw, 784px" /><figcaption class="wp-element-caption"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f517.png" alt="🔗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Link</strong>: <a href="https://sayonshubham.github.io/">https://sayonshubham.github.io/</a></figcaption></figure>
</div>


<p class="wp-block-paragraph">Now, when you try to <a href="https://stackoverflow.com/questions/44001007/scrape-the-absolute-url-instead-of-a-relative-path-in-python">scrape</a> the links as highlighted above, you find that only the relative links/paths are extracted instead of the entire absolute path. Let us have a look at the code given below, which demonstrates what happens when you try to extract the <code>'href'</code> elements normally.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">from bs4 import BeautifulSoup
import urllib.request
from urllib.parse import urljoin
import requests

web_url = 'https://sayonshubham.github.io/'
headers = {"User-Agent": "Mozilla/5.0 (CrKey armv7l 1.5.16041) AppleWebKit/537.36 (KHTML, like Gecko) "
                         "Chrome/31.0.1650.0 Safari/537.36"}
# get() Request
response = requests.get(web_url, headers=headers)
# Store the webpage contents
webpage = response.content
# Check Status Code (Optional)
# print(response.status_code)
# Create a BeautifulSoup object out of the webpage content
soup = BeautifulSoup(webpage, "html.parser")
for i in soup.find_all('nav'):
    for url in i.find_all('a'):
        print(url['href'])</pre>



<p class="wp-block-paragraph"><strong>Output:</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">/
/about
/blog
/finxter
/</pre>



<p class="wp-block-paragraph">The above output is not what you desired. You wanted to extract the absolute paths as shown below:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">https://sayonshubham.github.io/
https://sayonshubham.github.io/about
https://sayonshubham.github.io/blog
https://sayonshubham.github.io/finxter
https://sayonshubham.github.io/</pre>



<p class="wp-block-paragraph">Without further delay, let us go ahead and try to extract the absolute paths instead of the relative paths. </p>



<h2 class="wp-block-heading">Method 1: Using <span class="has-inline-color has-luminous-vivid-orange-color">urllib.parse.urljoin()</span></h2>



<p class="wp-block-paragraph">The easiest solution to our problem is to use the <a href="https://docs.python.org/3/library/urllib.parse.html#urllib.parse.urljoin" target="_blank" rel="noreferrer noopener"><code>urllib.parse.urljoin()</code></a> method.</p>



<p class="wp-block-paragraph">According to the Python documentation: <code data-enlighter-language="generic" class="EnlighterJSRAW">urllib.parse.urljoin()</code> is used to construct a full/absolute URL by combining the “base URL” with another URL. The advantage of using the <code>urljoin()</code>&nbsp;is that it properly resolves the relative path, whether&nbsp;<code>BASE_URL</code>&nbsp;is the domain of the URL, or the absolute URL of the webpage.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">from urllib.parse import urljoin

URL_1 = 'http://www.example.com'
URL_2 = 'http://www.example.com/something/index.html'

print(urljoin(URL_1, '/demo'))
print(urljoin(URL_2, '/demo'))</pre>



<p class="wp-block-paragraph"><strong>Output:</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">http://www.example.com/demo
http://www.example.com/demo</pre>



<p class="wp-block-paragraph">Now that we have an idea about <code data-enlighter-language="generic" class="EnlighterJSRAW">urljoin</code>, let us have a look at the following code which successfully resolves our problem and helps us to extract the complete/absolute paths from the HTML page.</p>



<p class="wp-block-paragraph"><strong>Solution:</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">from bs4 import BeautifulSoup
import urllib.request
from urllib.parse import urljoin
import requests

web_url = 'https://sayonshubham.github.io/'
headers = {"User-Agent": "Mozilla/5.0 (CrKey armv7l 1.5.16041) AppleWebKit/537.36 (KHTML, like Gecko) "
                         "Chrome/31.0.1650.0 Safari/537.36"}
# get() Request
response = requests.get(web_url, headers=headers)
# Store the webpage contents
webpage = response.content
# Check Status Code (Optional)
# print(response.status_code)
# Create a BeautifulSoup object out of the webpage content
soup = BeautifulSoup(webpage, "html.parser")
for i in soup.find_all('nav'):
    for url in i.find_all('a'):
        print(urljoin(web_url, url.get('href')))</pre>



<p class="wp-block-paragraph"><strong>Output:</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">https://sayonshubham.github.io/
https://sayonshubham.github.io/about
https://sayonshubham.github.io/blog
https://sayonshubham.github.io/finxter
https://sayonshubham.github.io/</pre>



<h2 class="wp-block-heading">Method 2: Concatenate The Base URL And Relative URL Manually</h2>



<p class="wp-block-paragraph">Another workaround to our problem is to concatenate the base part of the URL and the relative URLs manually, just like two ordinary strings. The problem, in this case, is that manually adding the strings might lead to &#8220;one-off&#8221; errors &#8212; try to spot the extra front slash characters <code>/</code> below:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">URL_1 = 'http://www.example.com/'
print(URL_1+'/demo')

# Output --> http://www.example.com//demo</pre>



<p class="wp-block-paragraph">Therefore to ensure proper concatenation, you have to modify your code accordingly such that any extra character that might lead to errors is removed. Let us have a look at the following code that helps us to concatenate the base and the relative paths without the presence of any extra forward slash.</p>



<p class="wp-block-paragraph"><strong><em>Solution:</em></strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">from bs4 import BeautifulSoup
import urllib.request
from urllib.parse import urljoin
import requests

web_url = 'https://sayonshubham.github.io/'
headers = {"User-Agent": "Mozilla/5.0 (CrKey armv7l 1.5.16041) AppleWebKit/537.36 (KHTML, like Gecko) "
                         "Chrome/31.0.1650.0 Safari/537.36"}
# get() Request
response = requests.get(web_url, headers=headers)
# Store the webpage contents
webpage = response.content
# Check Status Code (Optional)
# print(response.status_code)
# Create a BeautifulSoup object out of the webpage content
soup = BeautifulSoup(webpage, "html.parser")
for i in soup.find_all('nav'):
    for url in i.find_all('a'):
        # extract the href string
        x = url['href']
        # remove the extra forward-slash if present
        if x[0] == '/':       
            print(web_url + x[1:])
        else:
            print(web_url+x)</pre>



<p class="wp-block-paragraph"><strong>Output:</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">https://sayonshubham.github.io/
https://sayonshubham.github.io/about
https://sayonshubham.github.io/blog
https://sayonshubham.github.io/finxter
https://sayonshubham.github.io/</pre>



<p class="has-global-color-8-background-color has-background wp-block-paragraph"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong><span style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">Caution:</span></strong> This is not the recommended way of extracting the absolute path from a given HTML page. In situations when you have an automated script that needs to resolve a URL but at the time of writing the script you don&#8217;t know what website your script is visiting, in that case, this method won&#8217;t serve your purpose, and your go-to method would be to use <code data-enlighter-language="generic" class="EnlighterJSRAW">urlljoin</code>. Nevertheless, this method deserves to be mentioned because, in our case, it successfully serves the purpose and helps us to extract the absolute URLs.</p>



<h2 class="wp-block-heading">Conclusion</h2>



<p class="wp-block-paragraph">In this article, we learned how to extract the absolute links from a given HTML page using BeautifulSoup. If you want to master the concepts of Pythons BeautifulSoup library and dive deep into the concepts along with examples and video lessons, please have a look at the following link and follow the articles one by one wherein you will find every aspect of BeautifulSoup explained in great details.</p>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" title="Web Scraping With Beautiful Soup" width="937" height="527" src="https://www.youtube.com/embed/videoseries?list=PLbo6ydLr984ZbU9VrB1ouj9CCJ80x4Xmo" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>



<p class="wp-block-paragraph"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f517.png" alt="🔗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Recommended</strong>: <a href="https://blog.finxter.com/web-scraping-with-beautifulsoup-in-python/" target="_blank" rel="noreferrer noopener">Web Scraping With BeautifulSoup In Python</a></p>



<p class="wp-block-paragraph">With that, we come to the end of this tutorial! Please <strong><a href="http://blog.finxter.com/subscribe" target="_blank" rel="noreferrer noopener">stay tuned</a></strong> and <strong><a href="https://www.youtube.com/channel/UCRlWL2q80BnI4sA5ISrz9uw" target="_blank" rel="noreferrer noopener">subscribe</a></strong> for more interesting content in the future.</p>



<p>The post <a href="https://blog.finxter.com/scraping-the-absolute-url-of-instead-of-the-relative-path-using-beautifulsoup/">Python BS4 &#8211; How to Scrape Absolute URL Instead of Relative Path</a> appeared first on <a href="https://blog.finxter.com">Be on the Right Side of Change</a>.</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Page Caching using Disk: Enhanced 
Minified using Disk

Served from: blog.finxter.com @ 2026-08-14 20:00:53 by W3 Total Cache
-->