<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Jonathan Joyner on Medium]]></title>
        <description><![CDATA[Stories by Jonathan Joyner on Medium]]></description>
        <link>https://medium.com/@jonathanjoyner?source=rss-1cf90926fe20------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*lzqrv1rgY154DgCltfM4lg.jpeg</url>
            <title>Stories by Jonathan Joyner on Medium</title>
            <link>https://medium.com/@jonathanjoyner?source=rss-1cf90926fe20------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Tue, 10 Nov 2026 06:05:14 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@jonathanjoyner/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Quick and Easy Gif Creation and Optimization with Python]]></title>
            <link>https://medium.com/thedevproject/quick-and-easy-gif-creation-and-optimization-with-python-5223814861e2?source=rss-1cf90926fe20------2</link>
            <guid isPermaLink="false">https://medium.com/p/5223814861e2</guid>
            <category><![CDATA[python]]></category>
            <category><![CDATA[image-processing]]></category>
            <category><![CDATA[python-programming]]></category>
            <category><![CDATA[python3]]></category>
            <category><![CDATA[image-editing]]></category>
            <dc:creator><![CDATA[Jonathan Joyner]]></dc:creator>
            <pubDate>Mon, 06 Jun 2022 17:08:49 GMT</pubDate>
            <atom:updated>2022-06-07T21:09:18.467Z</atom:updated>
            <content:encoded><![CDATA[<p>Generating gifs with python can be a pain and there is not much information out there on how it’s done.</p><p>In addition, there is no comprehensive Python library that I could find for optimizing gifs programmatically.</p><p>I did end up figuring out an efficient solution to making gifs programmatically in Python after hours of research and have distilled it into just the necessities. In this process, we’ll use two tools: the Pillow library and Gifsicle.</p><p>The <a href="https://pypi.org/project/Pillow/">Pillow library</a> allows us to manipulate images in various ways and <a href="https://www.lcdf.org/gifsicle/">Gifsicle</a> is a CLI tool used for gif manipulation. So let’s dive into how to edit gifs with Python.</p><h3>Using Pillow</h3><p>For this example, I’ll take this gif:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/1*73yQ6LxZ3l5SlorhS2OWUA.gif" /><figcaption>Gif</figcaption></figure><p>And put this hat on him:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/900/1*aGkNUWbZ4NfuDBuvlp_gpQ.png" /><figcaption>Hat</figcaption></figure><p>To start, we need to install Pillow:</p><pre>pip install Pillow</pre><p>and we also need to import the Image class from Pillow:</p><pre>from PIL import Image</pre><p>Now, we’ll want to get our images as Image objects. We can run the code below (with your own file path) to show the images and make sure everything is working:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/332/1*xdetAF4zMrTzM3TRMVNhRw.png" /><figcaption>Show Images</figcaption></figure><p>The gif will only show the first frame, that’s normal. If nothing shows up, check your import statement and file path.</p><p>For now, we’ll work with just the first frame of the gif and swap over to the full gif once we have everything looking good on the first frame.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/340/1*EflMFhoUzsCsnl6Ld3UX4g.png" /></figure><p>Because my hat image has a transparent background, I use the mask argument. If your image doesn’t skip that part.</p><p>And here’s what we get:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/1*lws8dqhrCTJ_7z2bE_fizA.gif" /><figcaption>Hat Pasted</figcaption></figure><p>As you can see, the hat color isn’t correct but that’s okay! That will fix itself later.</p><p>Now that we have our images together. We just need to position the hat. We can do this by altering the arguments in the paste method:</p><pre>gif.paste(hat, (375, -55), mask = hat)</pre><p>Here I’ve added the amount of offset in the x and y directions as a tuple. The hat image will be offset by 375px on the x-axis and -55px (going up instead of down) in the y-axis.</p><p>Now we are set up on our images:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/1*aIK0mrDlLr1wL4ZIZwd-vw.gif" /><figcaption>Hat Positioned</figcaption></figure><p>Now we just need to paste the hat in this position over each frame of the gif. We do this by importing the ImageSequence object from PIL and using it to grab all of the frames:</p><pre>from PIL import Image, ImageSequence</pre><p>In the code below, I copy each from the iterator which iterates over the gif frames. From there, I paste the hat on the frame and append that frame to the list of created frames.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/426/1*PQ7Q0YoUG_17mWjk-Z-s1A.png" /><figcaption>Generating All Gif Frames</figcaption></figure><p>Once the frames are made, we have to save the file to view the new gif:</p><pre>new_gif[1].save(<br>    &#39;gif_creation/new_gif.gif&#39;,<br>    save all = True, <br>    appen_images = new_gift[1:],<br>    loop = 0<br>)</pre><p>This method saves the first frame and appends all other frames of the gif. The loop argument allows us to either set a number of loops or use 0 for infinite loops.</p><p>Once saved we have a new gif:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/1*yTH-6ZN8BsrZxzNZmh6xRg.gif" /></figure><p>And now for the full code:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/425/1*zeaIXfkh82w87TMR9I6Vww.png" /><figcaption>Gif Creation Code</figcaption></figure><p>Not too bad. At this point, the gifs could be generated and used as needed.</p><p>However, in my personal project, I was creating very large gifs which could not be uploaded to image hosting platforms.</p><h3>Optimizing the Gif</h3><p>So I had to go one step further and optimize the gifs. There are not many good options in Python for gif optimization. There is one excellent CLI tool that does just that called <a href="https://www.lcdf.org/gifsicle/">Gifsicle</a>.</p><p>If you’re not familiar, CLI (command-line interface) tools are used in the command line. One example of a CLI tool would be Git.</p><p>In order to use Gifsicle, you’ll need to download it.<a href="https://www.lcdf.org/gifsicle/"> All the download links can be found here</a>.</p><p>Once downloaded, it’s extremely easy for us to run gifsicle commands in Python. We can simply run a subprocess with all of the items in the command line as a list.</p><p>For this particular example, we’ll reduce the size of the gif through lossy compression and limit the number of colors used. There are many other ways Gifsicle can be used to optimize gifs, <a href="https://ezgif.com/">ezgif.com</a> uses it and shows some great examples of all the possibilities with the tool.</p><p>The following code runs optimization after the gif creation:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/843/1*B_K4qcMjN23PeG1Z3S7fEw.png" /><figcaption>Optimized Gif Creation</figcaption></figure><p>And we get out this gif:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/1*genGzfQxBK1on_Bm5eGDJg.gif" /></figure><p>As you can see, it has been compressed. The overall size of the gif dropped from 8.3MB to 3.1MB.</p><h3>Conclusion</h3><p>This is the basics of gif generation and optimization with Python. There is much more that can be done using just the methods shown in this example.</p><p>Creating gifs over other gifs and even moving the hat from frame to frame is relatively easy to do which allows for pretty much any type of gif generation.</p><p>Hopefully, this has saved you some time reading docs on these tools and gives you a head start on your gif generation journey.</p><p><em>If this helped you out, consider </em><a href="https://twitter.com/jonathanrjoyner"><em>following me on twitter </em></a><em>or </em><a href="https://medium.com/@jonathanjoyner"><em>here on Medium</em></a><em>.</em></p><p><em>Sign up for our </em><a href="https://newsletter.thedevproject.io/"><em>Free Weekly Newsletter</em></a><em>. Fresh content from developers all over the world, fresh and direct from the field! Don’t forget to follow our publication </em><a href="https://medium.com/thedevproject"><em>The Dev Project</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5223814861e2" width="1" height="1" alt=""><hr><p><a href="https://medium.com/thedevproject/quick-and-easy-gif-creation-and-optimization-with-python-5223814861e2">Quick and Easy Gif Creation and Optimization with Python</a> was originally published in <a href="https://medium.com/thedevproject">The Dev Project</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Powerful Twitter Sentiment Analysis in Under 35 Lines of Code]]></title>
            <link>https://medium.com/thedevproject/powerful-twitter-sentiment-analysis-in-under-35-lines-of-code-a80460db24f6?source=rss-1cf90926fe20------2</link>
            <guid isPermaLink="false">https://medium.com/p/a80460db24f6</guid>
            <category><![CDATA[python]]></category>
            <category><![CDATA[artificial-intelligence]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[machine-learning]]></category>
            <dc:creator><![CDATA[Jonathan Joyner]]></dc:creator>
            <pubDate>Wed, 06 Apr 2022 19:41:31 GMT</pubDate>
            <atom:updated>2022-04-06T21:48:24.601Z</atom:updated>
            <content:encoded><![CDATA[<h4>Python, Tweepy, and Flair have made it dead simple.</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*gozxljoBiEQzFDxm" /><figcaption>Photo by <a href="https://unsplash.com/@rswebsols?utm_source=medium&amp;utm_medium=referral">Souvik Banerjee</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Sentiment analysis can be as hard or as simple as you make it. Just like with any ML project you can pick the algorithm, train the model, and handle all of the issues that come along with it (hello, overfitting 😒).</p><p>I’ve been through that and I try to choose the easy road where possible. In the case of sentiment analysis, the work has been done. No need to reinvent the wheel.</p><p>So let’s have at it. We’ll only be using two libraries:</p><ul><li><a href="https://www.tweepy.org/">Tweepy</a></li><li><a href="https://github.com/flairNLP/flair">Flair</a></li></ul><p>Flair is a NLP framework developed by Humboldt University of Berlin. They have models trained on social media data, which is perfect for our use here.</p><p>Tweepy is an open-source Python library for accessing the Twitter API.</p><h3>Setup</h3><p>We’ll need a few libraries installed to get this project off the ground:</p><pre>pip install tweepy<br>pip install flair</pre><p>Flair takes a while to install, it’s quite a large library. While it’s installing, head over to <a href="https://developer.twitter.com/en">Twitter’s developer platform</a> and sign up for an account.</p><p>Once you have an account, open the developer portal and make a new project and click the key icon next to it to get your API keys and authentication tokens.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vF5cFX9GmtmEMCkruUGvvA.png" /><figcaption>Developer Dashboard</figcaption></figure><p>We’ll only be using v1.1 endpoints, so no need to worry about getting v2 access or any elevated access. The most basic account will do.</p><p>You’ll need four keys for this project, they’ll be labeled:</p><ul><li>API Key</li><li>API Secret</li><li>Access Token</li><li>Access Secret</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yQfgCcDGNTyODtWkMgm6Hg.png" /><figcaption>Keys and Tokens Page</figcaption></figure><p>Once you have those saved somewhere, our setup is done. We can dive into a Python file and start putting everything together.</p><h3>Getting Tweets</h3><h4>Setting the API Handler</h4><p>We’ll start by making sure we can get tweets from Twitter’s API. Let’s import tweepy and set those keys and tokens we generated:</p><pre>import tweepy</pre><pre>API_Key = &#39;Your API Key&#39;<br>API_Secret = &#39;Your API Secret&#39;<br>Access_Token = &#39;Your Access Token&#39;<br>Accesss_Secret = &#39;Your Access Secret&#39;</pre><p>Alright, now that we have our variables set, let’s set up an API handler. We can do this by using tweepy’s OAuthHandler class:</p><pre>auth = tweepy.OAuthHandler(API_Key, Secret_Key)</pre><p>With that new auth object, we want to use the set_access_token method to set our access tokens:</p><pre>auth.set_access_token(Access_Token, Access_Secret)</pre><p>Finally, we can set finish our API handler setup by creating a tweepy API class using that auth object:</p><pre>api = tweepy.API(auth)</pre><p>Let’s have a look at what our code should look like at this point:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/625/1*UCJXVlNJi_uWdjUtV6UJXA.png" /><figcaption>API Handler Set</figcaption></figure><h4>Searching Tweets</h4><p>The Twitter API is pretty robust, there are many ways to gather tweets from certain users, followers, etc. In this example, we’ll just search tweets. This works the same as using the search feature on Twitter.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/994/1*rXt7kclRhV194Ellzcn6iA.png" /><figcaption>Twitter Search</figcaption></figure><p>To search tweet’s we’ll use our api object’s <a href="https://docs.tweepy.org/en/stable/api.html#search-tweets">search_tweets method</a>. This takes in a few different arguments but we’ll just set a query, language, count, and the tweet_mode:</p><pre>tweets_data = api.search_tweets(<br>    q = &#39;test -RT&#39;,<br>    lang = &#39;en&#39;,<br>    count = 10,<br>    tweet_mode = &#39;extended&#39;<br>)</pre><p>If you noticed that our query has “-RT” in it, that is to exclude Retweets. That just makes things slightly simpler to work with.</p><p>That should be everything you need to search for tweets. Just print the tweets_data to make sure everything is up and running.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/376/1*jZWg_AzSMDthB-ic1k3ZQg.png" /><figcaption>Tweet Data Printed</figcaption></figure><p>You should get a big list of all kinds of data. The Twitter API will send everything back, including all kinds of information we won’t use.</p><p>If you didn’t get that, you likely got an authentication error:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/354/1*65vbJu-jJGEnF1yxHsU-Aw.png" /><figcaption>Authentication Error</figcaption></figure><p>This means you’re having issues with your keys and tokens. Make sure they’re in the right spot. If they are, try regenerating them because there was likely an error when you copied them over.</p><h4>Getting the Right Tweet Data</h4><p>So now that we’ve got a bunch of data printed, we need to trim it down to just the data we want. For now, we’ll just grab the text to run sentiment on.</p><p>We can do this pretty easily. Our tweet search outputs a list of tweets and we can use an attribute in tweepy’s model to grab the text:</p><pre>tweet_text = [tweet.full_text for tweet in tweets_data]</pre><p>That will output only the text associated with the tweet. If we print out tweet_text we’ll get a list of tweets (as strings):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/520/1*9XLY9JcjG2pNWhkcp-6vZw.png" /><figcaption>Tweet Text Printed</figcaption></figure><p>From here we can start analyzing the sentiment of these tweets.</p><h3>Sentiment Analysis</h3><p>Flair is a pretty large library. There are only a few pieces of the library we are going to use, so we’ll specify those imports to make our code as readable as possible:</p><pre>from flair.models import TextClassifier<br>from flair.data import Sentence</pre><p>The first thing we’ll need to do with flair is set up our classifier:</p><pre>classifier = TextClassifier.load(&#39;sentiment&#39;)</pre><p>Next, we’ll set up the sentences which will be used for analysis:</p><pre>tweet_sent = [Sentence(tweet) for tweet in tweets]</pre><p>Finally, we’ll use the classifier to predict each tweet sentiment:</p><pre>classifier.predict(tweet_sent, mini_batch_size=32)</pre><p>So just for a recap on where we are in our program, here’s a screenshot with the tweet_sent printed out:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/577/1*6kDcSg2elf5kNcGI4mqBIg.png" /><figcaption>Sentences Printed</figcaption></figure><p>As you can see, Flair gives us the tokenized sentence. What we are really after though, are the labels. Let’s separate out just the “POSITIVE” and “NEGATIVE” labels:</p><pre>tweet_sent = [sent.labels[0].value for sent in tweet_sent]</pre><p>So now we get a printout of just the label given to the tweet:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/545/1*JXw3X51N-BE5XNIZ6w40UQ.png" /><figcaption>Labels Printed</figcaption></figure><p>Now we have the tweets pulled and sentiment analyzed. We can simply combine the text with the sentiment.</p><pre>analyzed_tweets = list(zip(tweet_text, sent_values))</pre><p>Printing those out, we can see which tweets ranked positively and negatively.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/486/1*now6Atp-MWQeowOIiioYvw.png" /><figcaption>Tweet Sentiment</figcaption></figure><h3>Conclusion</h3><p>So this covers a lot of ground, we’ve:</p><ul><li>Generated API keys and authorization tokens for Twitter</li><li>Used tweepy to gather tweets</li><li>Used Flair to analyze the sent</li></ul><p>Just one more check on how much code that took:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/622/1*JcyCppR8sxabeiRsyCR7Ew.png" /><figcaption>Final Code</figcaption></figure><p>Right at 33 lines of code. That is an incredibly succinct program.</p><p>This is just scraping the surface of what both tweepy and Flair can do. You could build applications to analyze the sentiment of responses to tweets, followers sentiment on a subject, or even analyze sentiment in real time with streamed tweets!</p><p><em>If this helped you out, the best way to support me is by </em><a href="https://twitter.com/JonathanRJoyner"><em>following me on Twitter</em></a><em> or </em><a href="https://medium.com/@jonathanjoyner"><em>here on Medium</em></a><em>!</em></p><p><em>Feedback is my friend, so feel free to reach out and tell me that you liked my story, want some topic covered, or that some part of this could be done better.</em></p><p><em>Sign up for our </em><a href="https://newsletter.thedevproject.io/"><em>Free Weekly Newsletter</em></a><em>. Fresh content from developers all over the world, fresh and direct from the field! Don’t forget to follow our publication </em><a href="https://medium.com/thedevproject"><em>The Dev Project</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a80460db24f6" width="1" height="1" alt=""><hr><p><a href="https://medium.com/thedevproject/powerful-twitter-sentiment-analysis-in-under-35-lines-of-code-a80460db24f6">Powerful Twitter Sentiment Analysis in Under 35 Lines of Code</a> was originally published in <a href="https://medium.com/thedevproject">The Dev Project</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Scrape JavaScript Heavy Sites Like a Pro With Python]]></title>
            <link>https://medium.com/thedevproject/how-to-scrape-javascript-heavy-sites-like-a-pro-with-python-1ecf6f829538?source=rss-1cf90926fe20------2</link>
            <guid isPermaLink="false">https://medium.com/p/1ecf6f829538</guid>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[data-analysis]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[web-scraping]]></category>
            <category><![CDATA[python-programming]]></category>
            <dc:creator><![CDATA[Jonathan Joyner]]></dc:creator>
            <pubDate>Mon, 04 Apr 2022 18:41:10 GMT</pubDate>
            <atom:updated>2022-04-04T21:56:33.506Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*5GB1bQdP5hc_GCyf" /><figcaption>Photo by <a href="https://unsplash.com/@lautaroandreani?utm_source=medium&amp;utm_medium=referral">Lautaro Andreani</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>JavaScript is everywhere. If you find a website with no JavaScript on the page, you can bet it’s from the 1990’s.</p><p>That presents problems for web scraping. Most of the time, the data is right in the HTML of the page. It can be easily seen and scraped. However, there are times when the data is only available after the JavaScript is rendered.</p><p>That presents a challenge for web scraping. I’ve written <a href="https://medium.com/thedevproject/web-scraping-with-python-for-beginners-c9fdf206d7db">past articles on web scraping</a> which focus on easy-to-use Python libraries.</p><p>Unfortunately, that method breaks when you introduce JavaScript rendering into the mix.</p><p>So how do we handle websites that trap their data behind a JavaScript rendering? Well, we’re going to have to use some more advanced tools.</p><h3>The Right Tool for the Job</h3><p>Usually, I would recommend a couple of go-to libraries for web scraping:</p><ul><li>Requests</li><li>BeautifulSoup</li></ul><p>These two tools can do a whole lot, even if you’re <a href="https://medium.com/thedevproject/hierarchical-web-scraping-with-python-f221b969b60a">going through several pages for data</a>. They can’t be used to render JavaScript though.</p><p>There is a whole collection of tools that are built for this type of job. These tools are group into a category known as <strong>Browser Automation</strong>.</p><p>The goal of a browser automation tool is to simulate the web browsing experience but have it automated so that it can be run at intervals or speeds that a person couldn’t achieve.</p><p>These are touted more for their website testing capabilities for website owners. They also happen to have everything we need to render JavaScript and scrape the underlying data.</p><p>Some of the more popular tools in this category are:</p><ul><li><a href="https://scrapy.org/">Scrapy</a></li><li><a href="https://playwright.dev/python/docs/intro">Playwright</a></li><li><a href="https://selenium-python.readthedocs.io/">Selenium</a></li></ul><p>In this example, we’ll focus on using Selenium. We’ll also use our trusty library BeautifulSoup to parse the response.</p><h3>Setting up the Workspace</h3><p>Since we are fully automating a web browser, a little more setup is required than just a few pip installs. We’ll need a few other things installed:</p><ul><li>Chrome (or other web browser, we’ll be using Chrome in this example)</li><li>ChromeDriver (web driver for Chrome)</li></ul><p>Go ahead and <a href="https://www.google.com/chrome/">install Chrome</a> if you would like to follow along. For the ChromeDriver install, we’ll use a handy Python library that will do that for us.</p><p>With that said, let’s go ahead and install the libraries we’ll be using:</p><pre>pip install selenium<br>pip install bs4<br>pip install chromedriver-autoinstaller</pre><p>Once those are all installed, we can start importing:</p><pre>import chromedriver_autoinstaller<br>from selenium import webdriver<br>from bs4 import BeautifulSoup</pre><p>The chromedriver_autoinstaller library will handle installing ChromeDriver and adding it to PATH if it is not already there, which takes a bit of work off our plate. We can do that with one simple line:</p><pre>chromedriver_autoinstaller.install()</pre><p>That’s pretty much it for setting up our environment. Just to recap, we pip installed selenium, bs4, and chromedriver-autoinstaller. Our Python file should now look like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/351/1*yJNMgGxtyLTh5iVYrk40kA.png" /><figcaption>Imports</figcaption></figure><h3>Getting The Webpage</h3><p>With our environment set up, we can begin requesting web pages. To do that we need to set up our webdriver object which selenium will use:</p><pre>driver = webdriver.Chrome()</pre><p>And we can go ahead and tell the driver to fetch a web page. In this example, we’ll be scraping <a href="https://www.rottentomatoes.com/browse/cf-dvd-streaming-all">Rotten Tomatoes Certified Fresh Movies</a>.</p><p>The data we are after (movie titles, ratings, etc.) can be found without rendering the JavaScript. However, parsing the data is much easier when it is rendered.</p><p>This page is rendered almost entirely with JavaScript, here is the site with JavaScript enabled:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1RMJHrvGls0EN6mJC3knHw.png" /><figcaption>JavaScript Enabled</figcaption></figure><p>And with JavaScript disabled:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7VPUVNH6pagl1sGx-PnHYg.png" /><figcaption>JavaScript Disabled</figcaption></figure><p>We can request this web page by using our driver objects “get” method:</p><pre>driver.get(&#39;<a href="https://www.rottentomatoes.com/browse/cf-dvd-streaming-all">https://www.rottentomatoes.com/browse/cf-dvd-streaming-all</a>&#39;)</pre><p>And we can get the html output using the page_source attribute:</p><pre>html = driver.page_source</pre><p>Just to recap, here is where we are at on our code:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/660/1*Y4CKYSFOtE0KRCGZkK5tvg.png" /></figure><h3>Parsing the HTML</h3><p>Now, Selenium can parse the data. We’ll use that in certain scenarios. However, BeautifulSoup will be our go-to for parsing the HTML. So let’s make a soup out of the page source:</p><pre>soup = BeautifulSoup(html, &#39;html.parser&#39;)</pre><p>Now, we should have something that looks like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/659/1*Tit1DV62dtVnk4K0LMf2mg.png" /><figcaption>Soup</figcaption></figure><p>If we were to print out this soup object that we’ve made. We get the full web page, minus some of the fancy formatting. Luckily, we don’t have to wait for the JavaScript to execute on this page.</p><p>In some cases, we will have to wait for JavaScript execution. Which can be done by either <a href="https://selenium-python.readthedocs.io/waits.html">Implicitly Waiting or Explicitly Waiting</a>.</p><p>Since we don’t have to worry about that, let’s find the information we’re looking for:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TfZcAj0k7mbxvunTHqQARQ.png" /><figcaption>Rendered Movies</figcaption></figure><p>It looks like all the movies we are looking for are located inside their own div with the class “mb-movie”.</p><p>Each of these hold the information about the individual movie:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TtPT60lYEqUERwpAPdQj4A.png" /><figcaption>Movie Info</figcaption></figure><p>We can get at each one of them and get the title, score, and release date easily with BeautifulSoup:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/468/1*k0r8IAx24M3BbCNxFCWv9Q.png" /><figcaption>Parsed Info</figcaption></figure><h3>Conclusion</h3><p>So we have covered quite a lot in a short span of time. Here’s a recap of what we’ve done:</p><ul><li>Installed Chrome</li><li>Installed ChromeDriver using a Python library</li><li>Pulled a JavaScript heavy web page using Selenium</li><li>Parsed and gathered data using BeautifulSoup</li></ul><p>Here’s one final look at where we ended up, with the data printed out in the terminal:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/661/1*zTvgtx_SF5Zv7oRdbWNDdA.png" /></figure><p><em>If this helped you out, the best way to support me is by </em><a href="https://twitter.com/JonathanRJoyner"><em>following me on Twitter</em></a><em> or </em><a href="https://medium.com/@jonathanjoyner"><em>here on Medium</em></a><em>!</em></p><p><em>Feedback is my friend, so feel free to reach out and tell me that you liked my story, want some topic covered, or that some part of this could be done better.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1ecf6f829538" width="1" height="1" alt=""><hr><p><a href="https://medium.com/thedevproject/how-to-scrape-javascript-heavy-sites-like-a-pro-with-python-1ecf6f829538">How to Scrape JavaScript Heavy Sites Like a Pro With Python</a> was originally published in <a href="https://medium.com/thedevproject">The Dev Project</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Hierarchical Web Scraping With Python]]></title>
            <link>https://medium.com/thedevproject/hierarchical-web-scraping-with-python-f221b969b60a?source=rss-1cf90926fe20------2</link>
            <guid isPermaLink="false">https://medium.com/p/f221b969b60a</guid>
            <category><![CDATA[scraping-with-python]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[scraping]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[beautifulsoup]]></category>
            <dc:creator><![CDATA[Jonathan Joyner]]></dc:creator>
            <pubDate>Tue, 29 Mar 2022 22:42:27 GMT</pubDate>
            <atom:updated>2022-03-31T19:19:24.685Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*j-v2CBpDDXxbu8nT" /><figcaption>Photo by <a href="https://unsplash.com/@kellysikkema?utm_source=medium&amp;utm_medium=referral">Kelly Sikkema</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>One of the more difficult tasks when web scraping is dealing with hierarchical data. That is, data that lives on different pages.</p><p>If you’re looking for a basic way to get data from each page without manually going through them, look no further. We’ll be using two popular Python libraries to do just that:</p><ul><li>Requests</li><li>BeautifulSoup</li></ul><p>In this example, we’ll be using <a href="https://www.imdb.com/chart/top/?ref_=nv_mv_250">IMDb’s Top 250 Movies</a>. You can follow along in <a href="https://www.kaggle.com/jonathanjoyner/web-scraping-with-python">this kaggle notebook!</a></p><h4>Getting Each Movie Title</h4><p>We’ll go over getting the basic information quickly since that is relatively simple. If you’d like more detail, I’ve covered this in <a href="https://medium.com/@jonathan.joyner94/web-scraping-with-python-for-beginners-c9fdf206d7db">a previous Medium article</a>.</p><p>We can see that all of the movie information is in the main table body:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-C96xBsIGQ-rf2DjWJo_xQ.png" /><figcaption>Table Body</figcaption></figure><p>Each movie has a row in this table. Inside those rows there are columns. The column that holds the name of the movie has a class of “titleColumn”:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*16_34Ua9MugciIy9mV2sog.png" /><figcaption>Title Column</figcaption></figure><p>Now, getting the title of the movie is easy from here. We can simply use the requests library to get the page and iterate through the rows. For each row we can navigate to the title column, and grab the movie title inside the &lt;a&gt; tag:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/665/1*ySLE6OvO9FBwes_v0-HpSg.png" /><figcaption>Movie Titles Printed</figcaption></figure><p>Likewise, we can get the release year in the same column which is inside the &lt;span&gt; tag:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/487/1*1uKUuIQv6tGPkYfHsEoY8g.png" /><figcaption>Movie Title and Year</figcaption></figure><h4>Diving Deeper</h4><p>Let’s say we want some information about each movie that isn’t on the Top 250 list page. We want to add the genre of movie it is, which can be found on the individual movie page:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/642/1*j9HyP7kN9LW_ncGyud3aJg.png" /><figcaption>Movie Genre</figcaption></figure><p>In the bottom left corner of the screenshot, we have the genre categories the movie belongs to. So we’ll need to go to each individual movie page to pull that information.</p><p>Luckily, the page is linked right in the main top 250 page. Clicking the movie title would send us here. If we open DevTools (click F12 while in the browser), we can see that this link is inside the &lt;a&gt; tag we used to get the movie title:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TAu7mb3f4ygnchBnCP7Z7Q.png" /><figcaption>Movie Title Link</figcaption></figure><p>The link is in the “href” portion of the &lt;a&gt; tag. Luckily BeautifulSoup gives us just the tool to grab that portion of the tag:</p><pre>link = item.find(&#39;a&#39;)[&#39;href&#39;]</pre><p>It’s very similar to the notation used to slice on strings or lists. In this case, we can grab the data from different elements inside a tag.</p><h4>Requesting Each Movie’s Page</h4><p>In our case, we’ll have to get this for each of the movies in the list which will mean iterating over the rows we created earlier:</p><pre>for row in rows:<br>    link = column.a[&#39;href&#39;]</pre><p>And we can print out the links to each of the movies:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/375/1*AgVcmi98sXSDIamVcip6yQ.png" /><figcaption>Movie Links</figcaption></figure><p>The links don’t have the long query string which we saw in DevTools, however it works the same and will take us to the correct page.</p><p>Now we just have to request that page, keep in mind that this isn’t the full link. We have to add on the domain portion:</p><pre>for row in rows:<br>    link = column.a[&#39;href&#39;]<br>    movie_page = requests.get(f&#39;https://imdb.com/{link}&#39;).content</pre><p>Once we have the contents of the page, we need to parse it to get at the genre section:</p><pre>for row in rows:<br>    link = column.a[&#39;href&#39;]<br>    movie_page = requests.get(f&#39;https://imdb.com/{link}&#39;).content<br>    movie_soup = BeautifulSoup(movie_page, &#39;html.parser&#39;)</pre><h4>Finding the Movie Genres</h4><p>Now we just have to figure out where the genre name is located on the page:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*c1u5OW3g6aJ3ZvgmB_8IoQ.png" /></figure><p>So each genre is inside an &lt;a&gt; tag with a long class name. This can be easily added to the row iteration:</p><pre>for row in rows:<br>    link = column.a[&#39;href&#39;]<br>    movie_page = requests.get(f&#39;https://imdb.com/{link}&#39;).content<br>    movie_soup = BeautifulSoup(movie_page, &#39;html.parser&#39;)<br>    genre = movie_soup.find(&#39;span&#39;, &#39;sc-16ede01-3 bYNgQ ipc-chip ipc-chip--on-baseAlt&#39;)</pre><p>If we were to print out the genre at this point, we get something unexpected:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/722/1*VQS4x_POedBeoM4MfUlOEg.png" /></figure><p>We only print out one genre per movie. Specifically, the first genre in the html.</p><p>Of course, we know a movie can have multiple which is shown in screenshots of The Godfather above. So we need to iterate through the genres. We can do this by using the “find_all” method and using some list comprehension:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/797/1*ZhLxnAnH6kJjYOR0ZmBZPg.png" /><figcaption>Multiple Genres</figcaption></figure><p>And finally, we can attach it to the main movie names and release dates:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/801/1*39CMGOzrdnHCViWuqvhW6w.png" /><figcaption>Movies with year and genres</figcaption></figure><p>This is as deep as we’ll go for this example but you can go much more than two layers deep into the hierarchy using this method.</p><h4>Conclusion</h4><p>This has covered quite a lot, you should now be able to:</p><ul><li>Request a web page</li><li>Pull information from that page</li><li>Find other web pages that contain information you want</li><li>Gather information from those pages</li></ul><p>On pretty much any website. Let me know if you have any questions in the comments!</p><p><em>If this helped you out, consider </em><a href="https://twitter.com/jonathanrjoyner"><em>following me on twitter </em></a><em>for more daily programming tips and articles.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f221b969b60a" width="1" height="1" alt=""><hr><p><a href="https://medium.com/thedevproject/hierarchical-web-scraping-with-python-f221b969b60a">Hierarchical Web Scraping With Python</a> was originally published in <a href="https://medium.com/thedevproject">The Dev Project</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Web Scraping With Python for Beginners]]></title>
            <link>https://medium.com/thedevproject/web-scraping-with-python-for-beginners-c9fdf206d7db?source=rss-1cf90926fe20------2</link>
            <guid isPermaLink="false">https://medium.com/p/c9fdf206d7db</guid>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[scraping]]></category>
            <category><![CDATA[scraping-with-python]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[python]]></category>
            <dc:creator><![CDATA[Jonathan Joyner]]></dc:creator>
            <pubDate>Tue, 29 Mar 2022 17:45:15 GMT</pubDate>
            <atom:updated>2022-03-31T19:22:36.479Z</atom:updated>
            <content:encoded><![CDATA[<h4>A guide to using Requests and BeautifulSoup on HTML pages</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*n23O52Iy1peo_F7n" /><figcaption>Photo by <a href="https://unsplash.com/@artnok?utm_source=medium&amp;utm_medium=referral">Nicolas Picard</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Scraping the internet for data can be one of the most challenging steps in a development project. It doesn’t have to be.</p><p>Here we will focus on scraping basic HTML pages using two popular Python libraries:</p><ul><li>Requests</li><li>BeautifulSoup</li></ul><p>Both of these libraries have ample documentation and plenty of issues resolved on StackOverflow, which make them easy to use and troubleshoot if problems arise.</p><p>In this example, we’ll be using <a href="https://www.imdb.com/chart/top/?ref_=nv_mv_250">IMDb’s Top 250 Movies</a>. You can follow along in <a href="https://www.kaggle.com/jonathanjoyner/web-scraping-with-python">this kaggle notebook!</a></p><h4>Installing the Libraries</h4><p>These libraries will be installed using <a href="https://pip.pypa.io/en/stable/getting-started/">pip</a>, the package installer for Python. Open your terminal or command prompt and run this:</p><p>pip install requests</p><p>pip install bs4</p><p>Once you’ve run both of those, you should have everything you need to begin web scraping.</p><h4>Importing the Libraries</h4><p>Once you have the libraries installed, open up python file in whichever editor you like (<a href="https://www.kaggle.com/code/jonathanjoyner/web-scraping-with-python-for-beginners">or open the kaggle notebook for this article</a>) and run the following imports:</p><pre>import requests<br>from bs4 import BeautifulSoup</pre><h4>Running Requests and Making a Soup</h4><p>Let’s run a request to get the html from the page:</p><pre>html = requests.get(&#39;<a href="https://www.imdb.com/chart/top/?ref_=nv_mv_250&#39;).content">https://www.imdb.com/chart/top/?ref_=nv_mv_250&#39;).content</a></pre><p>If we were to print our html, we would see the raw html without any formatting. This is because we grabbed the response as bytes using the “content” attribute.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/672/1*5EMDdKw7PZo73EO6hCi1Rw.png" /><figcaption>Unparsed HTML</figcaption></figure><p>So we now need to parse the html, which is where BeautifulSoup comes in:</p><pre>soup = BeautifulSoup(html, &#39;html.parser&#39;)</pre><p>Now if we print out our soup object, we get parsed html:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/673/1*TifDr-uzpnnP8qrXFbGBcw.png" /><figcaption>Parsed HTML</figcaption></figure><h4>Getting the Data</h4><p>Now that we’ve collected the html page and parsed it, we can grab whatever data we would like to. This is done by using different methods and attributes on the soup object we created (we called this “soup”).</p><p>The easiest way to find elements in the html is through the use of DevTools in your browser. You can activate this by opening up the web page and pressing F12.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/973/1*nkXegpinLS0ePguBS7oYRQ.png" /><figcaption>Open DevTools</figcaption></figure><p>You can then hover over elements of the page to quickly find the ones that may be most useful. In this case, we can see the movie list is made up of one large table:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-C96xBsIGQ-rf2DjWJo_xQ.png" /><figcaption>Movie Table</figcaption></figure><h4>Getting Movie Titles</h4><p>First, let’s get the title of each movie on the list. In our code we can narrow down the html we are looking at by only looking at the &lt;tbody&gt;&lt;/tbody&gt; portion, since we only care about the movies inside the table.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/673/1*WQdqHX3JLr_GNnC5bfmK-Q.png" /><figcaption>Table Body</figcaption></figure><p>Now that we’ve narrowed down what we have to look at, it’s easier to tell where the data we want is. Each movie has its own row in that table:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*A-H9IxROUZnEvmp-VFni-w.png" /><figcaption>Table Row</figcaption></figure><p>We can simplify what we are looking at further by having our soup return the rows of the table as a list:</p><pre>rows = soup.tbody.find_all(&#39;tr&#39;)</pre><p>Now that we have a list of rows, we can print one row and find the data we want. The title can be found in a column with the class “titleColumn”:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*16_34Ua9MugciIy9mV2sog.png" /><figcaption>Title Column</figcaption></figure><p>So we will need to find a column with that class for each row. Now that we’ve created a list of rows, we’ll have to iterate over that list:</p><pre>for row in rows:<br>    column = row.find(&#39;td&#39;, &#39;titleColumn&#39;)</pre><p>If we were to print out the text of “column” which we just created, we would see extra information besides just the movie title:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/379/1*d00xTwG2vV8w4tLMKBZ2bA.png" /><figcaption>Title Column Text</figcaption></figure><p>This is because BeautifulSoup grabs all of the text within the title column. So we’ll have to narrow it down further.</p><p>The title is within the only &lt;a&gt; tag in the title column.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TAu7mb3f4ygnchBnCP7Z7Q.png" /><figcaption>Movie Title</figcaption></figure><p>So we can easily pull the title by telling BeautifulSoup to get the text from the &lt;a&gt; tag:</p><pre>for row in rows:<br>    column = row.find(&#39;td&#39;, &#39;titleColumn&#39;)<br>    title = column.a.text</pre><p>If we print the titles, we get a neat list of all the titles on IMDb’s Top 250:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/402/1*8KrIK08Izbz5Gyaswu1TxQ.png" /><figcaption>Movie Titles Printed</figcaption></figure><h4>Getting Movie Release Years</h4><p>We can also do this with the year by getting the text from the&lt;span&gt; tag.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QXIQ-k5Ix4IRN1LlD8cdKQ.png" /><figcaption>Movie Year</figcaption></figure><p>Since this is in the same column as the title, the only difference is using column.span instead of column.a :</p><pre>for row in rows:<br>    column = row.find(&#39;td&#39;, &#39;titleColumn&#39;)<br>    title = column.a.text<br>    year = column.span.text</pre><p>Now we can print out the title of each movie along with the release year:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/487/1*1uKUuIQv6tGPkYfHsEoY8g.png" /><figcaption>Movie Titles and Years</figcaption></figure><p>Of course, we can do a great deal more with these two tools. The user ratings and rank are easy to grab from here as well.</p><h4>Conclusion</h4><p>Just to reiterate, the steps we took to get this information was:</p><ul><li>Request a web page (using the requests library)</li><li>Parse the HTML (using BeautifulSoup)</li><li>Search through the parsed HTML for the piece of information we want</li></ul><p>To dive a little deeper, check out how you can <a href="https://medium.com/@jonathan.joyner94/hierarchical-web-scraping-with-python-f221b969b60a">go into each movie page and get the genres for each movie</a> using the same tools we used here!</p><p><em>If this helped you out, consider </em><a href="https://twitter.com/jonathanrjoyner"><em>following me on twitter </em></a><em>for more daily programming tips and articles.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c9fdf206d7db" width="1" height="1" alt=""><hr><p><a href="https://medium.com/thedevproject/web-scraping-with-python-for-beginners-c9fdf206d7db">Web Scraping With Python for Beginners</a> was originally published in <a href="https://medium.com/thedevproject">The Dev Project</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>