Instagram This Week
What kind of cruiser?
Bored of the lack of thoughtfulness and reading comprehension on a cruising forum I frequent I post the following post (the original post). I got some good feedback on my attempt at humour so I thought I would repost it and a few of my follow-up posts…
Warning: the following is mostly tongue-in-cheek, although there is an underlying truth to the issue.
It seems to me that many of the discussions on CF go off the rails because we are all so different in both our ambitions and realities. “How much does it cost to cruise?” You’d think this was a pretty straightforward question but it really isn’t and the topic generally goes sideways faster than a drunken docking attempt on a windy day. Even seemingly innocuous discussions like “How much chain do I need in such-and-such area” often become completely nonsensical because no one can agree on what we are actually talking about.
It seems to me we all stubbornly live in our own bubbles. Of course he means to be on the hook 99% of the time! vs. No sensible person would ever anchor out if there was a marina nearby! is never considered when joining the discussion—we just dive in and start the pontificating.
So I propose we find a solution.
I started with a simple chart. “That,” I said to myself, “would solve everything.” “It’s simple Self,” I said, “Simply find your place on the chart and colour code your answers so as to clear up any potential misunderstandings with all our fellow CFers.”

But then I got to thinking. It just wasn’t enough. Where was the geography? We needed a plane for warm vs cold, anchorages vs moorings… And what about round-the-world vs regional? Or a scale for single-handers all the way up to large crew?
(I tried to make a chart—it turned into a disaster )

This was getting complicated. So maybe a chart is out. How about codes?
Crew size |Crew Period |Climate | Expenses | Area | Moorage Style etc.
So for example I would be a 2PTCMRA (2 crewed, part-time, cold-water, moderate spending, regional anchorer). We could have a big chart … or an app…this is a great opportunity for an app developer—wait…ummm, we need a code for electronics too, with sub codes for radios, GPS, trackers, weather systems… hmmm…
So… ya. A chart. A BIG chart! We will need codes for anchor types too, and gun preferences, probably sub charts for specific regions cause we all know that the Med is different kettle of fish than the Caribbean— I mean literally, totally different fish and what we have here in the PNW is obviously so much better than anywhere else so…but I digress.
We will need indicators for partiers, outgoing folk, introverts, singles, people who can rebuild transmissions blindfolded, those who are still unsure what the pointy bits on a hammer are for… Oh, and most especially some way to differentiate where we fall on the scale of way over-prepared to “I bought a boat on the internet and cast off tomorrow—can someone show me how to sail?”
I guess we need a code for sailing purists. And one for those of us who have a funny stick in the middle of our powerboats. And racers. And dock-bound liveaboards. Is specifying whale preferences going too far? I like orca myself. What the hell let’s add it in.
So now would start any thread participation with “I am a 2PTCMRAPNWNGIMIEMPSO…” and as a result any potential misunderstandings would immediately be averted.
Ok so that about covers it. I am starting to grid this out and will announce the official chart when its completed and will have to get the mods onboard to enforce total compliance. Won’t work otherwise and this would have been a total waste of effort. Hmmm, I guess we will have to access penalties as well. Maybe a grid to determine just how serious breaches, cases of mis-information, and what the levels of incompleteness are and empower Guardians of the Grid to deliver appropriate punishments. That can be phase 2.
This is going to be so cool!
Or… I guess… We could…I mean… Maybe…
Could we all just take a moment to thank god (and/or whatever diety, spirit or scientific principle you believe in) that we are all able to get out on the water in whatever way we can and that that is a whole lot of different ways?
Seriously, if we all just took a moment to consider an OP’s situation or even a fellow thread participant’s perspective there would be so many fewer stupid, inadvertent pissing contests. And then we could get on with the intentional ones.
Just a thought. Let me know if you still want me to go ahead with the grid idea.
The thread continued. Both Mike O’ and L independently thought Venn diagrams were the way to go:
Maybe what you need is a series of Venn diagrams. Cruisers are liveaboards, but not all liveaboards are cruisers. Racers and cruisers overlap, but some are just racers, and some are just cruisers. Some cruisers anchor out, some marina hop, and some do both.
So I gave this a try:

Then Wolfgal suggested:
What fun, Macblaze! Your ever-growing charts are so complicatedly fun! a holograph chart that actually turns around in space, popping up from my personal R2-D2 would take your idea to the next level. could you do this?. 
I thought, I can do that…

I almost lost control of the whole idea when Tayana42 tried to outsmart me with:
Curiosity
Reigns
Until
I’ve
Seen
Enough
Or
Casually
Roving
Upon
Iceless
Seas
In
No
Great hurry
Clever, clever! It went on for pages and pages after that.
Instagram This Week
Instagram This Week
Instagram This Week
Instagram This Week
Web scraping Python code
In my previous post I explained that I was looking for a way to use web scraping to extract data from my Calibre-Web shelves and automatically post them to my Books Read page here on my site. In this post I will step through my final Python script to explain to my future self what I did and why.
Warning: Security
A heads up. I have no guarantees that this code is secure enough to use in a production environment. In fact I would guess it isn’t. But my Web-Calibre webserver is local to my home network and I trust that my hosted server (macblaze.ca) is secure enough. But since you are passing passwords etc. back and forth I wouldn’t count on any of this to be secure without a lot more effort than I am willing to put in.
The code in bits
# import various libraries
import requests
from bs4 import BeautifulSoup
import re
This loads the various libraries the script uses. Requests is a http library that allows you to send requests to websites, BeautifulSoup is a library to pull data from html and re is a regex library to allow you to do custom searches.
# set variables
# set header to avoid being labeled a bot
headers = {
'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
}
# set base url
urlpath='http://urlpath'
# website login data
login_data = {
'next': '/',
'username': 'username',
'password': 'password',
'remember_me': 'on',
}
# set path to export as markdown file
path_folder="/Volumes/www/books/"
file = open(path_folder+"filename.md","w")
This sets up various variables used for login including a header to try and avoid being labeled a bot, the base url of the Calibre-web installation, login data and specifies a location and name for the resulting markdown file. The open command is marked with a ‘w’ switch to indicate the script will write a new file every time it is executed, overwriting the old one.
# log in and open http session
with requests.Session() as sess:
url = urlpath+'/login'
res = sess.get(url, headers=headers)
res = sess.post(url, data=login_data)
Then, using Requests, I opened a session on the webserver and log in using the variables.
Writing the File
Note: The code has matching file.write() and print() statements throughout. The print() statements just write to the terminal app and allow me to see what is being written to the actual file using file.write(). They are completely unnecessary.
# Set Title
file.write("# Books Read\n")
print("# Books Read\n")
Pretty basic: write the words Books Read followed by a carriage return, tagged with a # to indicate it is a h1 head. This will become the actual page name.
# find list of shelves
shelfhtml = sess.get(urlpath)
soup = BeautifulSoup(shelfhtml.text, "html.parser")
shelflist = soup.find_all('a', href=re.compile('/shelf/[1-9]'))
print (shelflist)
So now we set the variable shelfhtml to the session we opened earlier. Using BeautifulSoup we grab all the html code and search for all a links that have an href that contain the regex expression ‘/shelf/[1-9]’. (Hopefully I won’t have more than 9 shelves or I will have to redo this bit.) The variable now contains list of all the links that match that pattern and looks like this:
[<a href="/shelf/3"><span class="glyphicon glyphicon-list private_shelf"></span>2018</a>, <a href="/shelf/2"><span class="glyphicon glyphicon-list private_shelf"></span>2019</a>, <a href="/shelf/1"><span class="glyphicon glyphicon-list private_shelf"></span>2020</a>]
This as you can see, contains the links to all three of my current Year shelves, displayed in ascending numerical order.
#reverse order of urllist
dateshelflist=(get_newshelflist())
dateshelflist.reverse()
print (dateshelflist)
I wanted to display my book lists from newest to oldest so I used python to reverse the items in the list.
First loop: the shelves
The first loop loops through all the shelves (in this case 3 of them) and starts the process of building a book list for each.
# loop through sorted shelves
for shelf in dateshelflist:
#set shelf page url
res = sess.get(urlpath+shelf.get('href'))
soup = BeautifulSoup(res.text, "html.parser")
# find year from shelflist and format
shelfyear = soup.find('h2')
year = re.search("([0-9]{4})", shelfyear.text)
year.group()
file.write("### {}\n".format(year.group()))
print("### {}\n".format(year.group()))
In the first iteration of the loop, the script goes to the actual shelf page using the base url and then adding an href extracted from the list by using a get command and then accesses the html from the resulting webpage. Then the script finds the year info, which is a H2, extracts the 4-digit year with the regex ([0-9]{4}) and writes it to the file, formatted as an H3 header and followed by a line break.
# find all books
books = soup.find_all('div', class_='col-sm-3 col-lg-2 col-xs-6 book')
Using BeautifulSoup we extract the list of books from the page knowing they are all marked with a div in the class col-sm-3 col-lg-2 col-xs-6 book.
Second loop: the books
#loop though books. Each book is a new BeautifulSoup object.
for book in books:
title = book.find('p', class_='title')
author = book.find('a', class_='author-name')
seriesname = book.find('p', class_='series')
pubdate = book.find('p', class_='publishing-date')
coverlink = book.find('div', class_='cover')
if None in (title, author, coverlink, seriesname, pubdate):
continue
# extract year from pubdate
pubyear = re.search("([0-9]{4})", pubdate.text)
pubyear.group()
This is the beginning of the second loop. For each book we use soup to extract the title, author, series, pubdate and cover (which I don’t end up using). Each search is based on the class assigned to it in the original html code. Because I only want the pub year and not pub date, I again use a regex to extract the 4-digit year. The if None… statement is there just in case one of the fields is empty and prevents the script from hanging.
# construct line using markdown
newstring = "* ***{}*** — {} ({})\{} – ebook\n".format(title.text, author.text, pubyear.group(), seriesname.text)
file.write(newstring)
print (newstring)
Next we construct the book entry based on how we want it to appear on the web page. In my case I want each entry to be an li and end up looking like this:
- The Cloud Roads — Martha Wells (2011)
Book 1.0 of Raksura – ebook
Python allows you to just list the variables at the end of the statement and fills in the {} automatically which makes for easier formatting. The script then writes the line to the open markdown file and heads up to the beginning of the loop to grab the next book.
More loops
That’s pretty much it. It loops through the books until it runs out and heads back to the first loop to see if there is another shelf to process. After it processes all the shelves it drops to the last line of the script:
file.close()
which closes the file and that is that—c’est tout. It will now be accessed the next time some visits the Books Read page on my site.
In Conclusion
Hopefully this is clear enough so that when I forget every scarp of python in the years to come I can still recreate this after the inevitable big crash. The script, called scrape.py in my case, is executed in terminal by going to the enclosing folder and typing python3 scrape.py then hitting enter. Automating that is something I will ponder if this book list thing becomes my ultimate methodology for recording books read. It’s big failing is that it only records ebooks in my Calibre library. I might have to redo the entire thing for something like LibraryThing where I can record all my books…lol. Hmmm… maybe…
The Final Code
Here is the final script in its entirety.
# import various libraries
import requests
from bs4 import BeautifulSoup
import re
# set header to avoid being labeled a bot
headers = {
'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
}
# set base url
urlpath='http://urlpath'
# website login data
login_data = {
'next': '/',
'username': 'username',
'password': 'password',
'remember_me': 'on',
}
# set path to export as markdown file
path_folder="/Volumes/www/home/books/"
file = open(path_folder+"filename.md","w")
with requests.Session() as sess:
url = urlpath+'/login'
res = sess.get(url, headers=headers)
res = sess.post(url, data=login_data)
# Note: print() commands are purely for terminal output and unnecessary
# Set Title
file.write("# Books Read\n")
print("# Books Read\n")
# find list of shelves
shelfhtml = sess.get(urlpath)
soup = BeautifulSoup(shelfhtml.text, "html.parser")
shelflist = soup.find_all('a', href=re.compile('/shelf/[1-9]'))
# print (shelflist)
#reverse order of urllist
dateshelflist=(get_newshelflist())
dateshelflist.reverse()
# print (dateshelflist)
# loop through sorted shelves
for shelf in dateshelflist:
#set shelf page url
res = sess.get(urlpath+shelf.get('href'))
soup = BeautifulSoup(res.text, "html.parser")
# find year and format
shelfyear = soup.find('h2')
year = re.search("([0-9]{4})", shelfyear.text)
year.group()
file.write("### {}\n".format(year.group()))
print("### {}\n".format(year.group()))
# find all books
books = soup.find_all('div', class_='col-sm-3 col-lg-2 col-xs-6 book')
#loop though books. Each book is a new BeautifulSoup object.
for book in books:
title = book.find('p', class_='title')
author = book.find('a', class_='author-name')
seriesname = book.find('p', class_='series')
pubdate = book.find('p', class_='publishing-date')
coverlink = book.find('div', class_='cover')
if None in (title, author, coverlink, seriesname, pubdate):
continue
# extract year from pubdate
pubyear = re.search("([0-9]{4})", pubdate.text)
pubyear.group()
# construct line using markdown
newstring = "* ***{}*** — {} ({})\{} – ebook\n".format(title.text, author.text, pubyear.group(), seriesname.text)
file.write(newstring)
print (newstring)
file.close()
Note 12/2021
There has been an update to the Calibre web code so I had to make some changes to the python script.
Making a “Books Read” page
So recently I came across a web page called How I manage my ebooks by a fellow named Aleksandar Todorovi. He is a developer who wanted to track his reading on his webpage. He introduced me to a Calibre project called Calibre-Web which is basically a web interface for Calibre with a few extra bells and whistles. Reading through his explanation it seemed pretty simple to implement except for this statement:
As a final step in the chain, I have created a script that allow me to publish the list of books I’ve read on my website. Since Calibre-Web doesn’t have an API, I ended up scraping my own server using Python Requests and BeautifulSoup . After about one hundred lines of spaghetti code gets executed, I end up with two files:
books-read.md, which goes straight to my CMS, allowing me to publicly share the list of books I have read, sorted by the year in which I’ve finished reading them.…
The Process
So I set about to try and implement my own version of Aleksandar’s project. In my typical trial and error fashion it took a couple of days of steady work and I learned a ton along the way.
Calibre-Web
I went ahead and downloaded Calibre-Web and wrestled getting it running on my test server (my old mac-mini). It is a python script, which I still a bit fuzzy about the proper way to actually implement. I ended up writing a shell script to run the command "nohup python /Applications/calibre-web-master/cps.py" and them made it executable from my desktop. I still have some work to do there to finalize that solution.
I have to say I really like the interface of Calibre-Web much more than the desktop Calibre and although there are a few quirks, I will likely be using the web version much more than the desktop from now on.
Then I made a few shelves with the books I had read in 2019 and 2020 and was good to go. Now I just needed to get those Shelves onto my website somehow.
Web Scraping
Now I’ve never heard of the term web scraping, but the concept was familiar and it turns out it is quite the thing.
Web scraping, web harvesting, or web data extraction is data scraping used for extracting data from websites
—Web scraping, Wikipedia
The theory being that since all the info is available accessible in the basic code of the Calibre-Web pages, all I needed to do was extract and format it, then repost it to this site. So I did. Voila: My Books Read page.
I guess I skipped the tough part…
Starting out I understood Python was a programming language, but had no idea what Python Requests or BeautifulSoup were. Turns out that Python Requests was essentially one of many “html to text” interpreters and BeautifulSoup was a program (library?…I am still a bit vague on the terminology) to extract and format long strings of code into useful data.
Start with Google
I started by a quick search and found a few likely examples to follow along with.
https://medium.com/the-andela-way/introduction-to-web-scraping-87edf94ac692
https://medium.com/the-andela-way/learn-how-to-scrape-the-web-2a7cc488e017
https://www.dataquest.io/blog/web-scraping-beautifulsoup/
These were helpful in explaining the structure and giving me some basic coding ideas, but I mostly relied on https://realpython.com/beautiful-soup-web-scraper-python/ to base my own code on.
Step one
I got everything running (this included sorting out the mess that is python on my computer, but that is another story) and tried to get a basic python script to talk to my calibre installation. Turns out that even though my web browser was logged into Calibre-Web, my script wasn’t. Some some more googling found me this video (Website login using request library in Python) and it did the trick to write the login portion of my script.
Step two
Then I wrote a basic script that extracted data (much more on this later) and saved it to a markdown file on the webserver. I figured markdown was easier to implement than html and knew WordPress could handle it.
Or could it? Turns out the Jetpack implementation was choking on my markdown file for some reason. I fought with it for a while and eventually decided to see if I could find a different WordPress plugin to do the job. Turned out I could kill two birds with one stone using Mytory Markdown which would actually load (and reload) a correctly formatted remote .md file to a page every time someone visited.
Step three
After I got a sample page loaded on the website I realized that it was missing pub date and series name which, if you have ever visited one of my annual books read posts (Last Books of the decade: 2019, Books 2018—Is this the last year? etc.) is essential information. So I had to go into the Calibre-Web code and add those particular pieces of info to the shelf page so I would be able to scrape it all at the same time. I ended up adding this:
{% if entry.series|length > 0 %}
<p class="series">
{{_('Book')}} {{entry.series_index}} {{_('of')}} <a href="{{url_for('web.books_list', data='series',sort='abc', book_id=entry.series[0].id)}}">{{entry.series[0].name}}</a>
</p>
{% endif %}
{% if entry.pubdate[:10] != '0101-01-01' %}
<p class="publishing-date">{{entry.pubdate|formatdate}} </p>
{% endif %}
…to to shelf.html in /templates folder of the Calibre-Web install. I added it around line 45 (just after the {% endif %} for the author section). It took a bit of fussing to look good but it worked out great.
Step four
Now all I have to do is figure out how to run my scrape.py script. For now I will leave it a manual process and just run it after I update my Calibre-Web shelves, but making that automatic is on the list for “What’s Next…”
Ta-da
So between this post and Aleksandar’s I hope you have a basic idea of what you need to do in order to try and implement this solution. More importantly when future me comes back and tries to figure out what the hell all this gobbledey-gook mess is I can rebuild the system based on these sketchy notes. I will end this here and continue in a new post on the actual python/beautifulsoup code I came up with to get the web scraping done.









