<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Blog of Bernardas Ališauskas</title><link href="https://granitosaur.us/" rel="alternate"></link><link href="Blog%20of%20Bernardas%20Ali%C5%A1auskas/atom.xml" rel="self"></link><id>https://granitosaur.us/</id><updated>2020-01-02T00:00:00+01:00</updated><subtitle>Python programmer and a goof who loves free software, video-games and heavy metal</subtitle><subtitle>Python programmer and a goof who loves free software, video-games and heavy metal</subtitle><entry><title>Memory is Me</title><link href="https://granitosaur.us/memory-is-me.html" rel="alternate"></link><published>2020-01-02T00:00:00+01:00</published><updated>2020-01-02T00:00:00+01:00</updated><author><name>Bernardas Ališauskas</name></author><id>tag:granitosaur.us,2020-01-02:/memory-is-me.html</id><summary type="html">&lt;p&gt;New year resolution - understanding and figure out ways to improve memory.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've been slowly reaching mature adulthood as I've turned 27 just now and I've constantly poked around major meta physics and philosophy question as most of people have. While I hadn't found many answers yet one thing I've got certain - how valuable memories are. &lt;/p&gt;
&lt;p&gt;Objectively speaking our memory structure is often cited as one of the reason for our species intelligence and consciousness - an idea that has been explored in many books, shows and movies. I fully agree with this idea - what are we if not a biological machine with a collection of experiences and memories.&lt;/p&gt;
&lt;p&gt;Unfortunately as I started to get older I feel I'm starting to forget things. Some might be pure biology - I'm vegetarian so I often skimp on B12 and my diet is lazy on iron - but most of it can be blamed on method. By that I mean my current methods of preserving memories are flawed and in need of improvement.  &lt;/p&gt;
&lt;p&gt;So this is my new years resolution - improve on my memory management!&lt;/p&gt;
&lt;h1 id="understanding-and-improving-memory"&gt;Understanding and Improving Memory&lt;/h1&gt;
&lt;p&gt;To improve something you have to understand it - well at least a little bit. For that I've went through few resources and got a worthy summary.&lt;/p&gt;
&lt;p&gt;&lt;video autoplay="" height="240" loop="" muted="" title="Unfortunately you can't beam your memories as radio signals just yet :(" width="480"&gt;&lt;source src="/gifs/brain.mp4" type="video/mp4"/&gt;&lt;/video&gt;&lt;figcaption&gt;Unfortunately you can't beam your memories as radio signals just yet :(&lt;/figcaption&gt;&lt;/p&gt;
&lt;p&gt;There are many kinds of memory, but the one I'm interested for this topic is long term declarative memory. Without digging to much into this immense subject declarative memory refers to all memories that are consciously available - things you can remember on demand. This is further split into: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Semantic: knowledge about external world - facts.&lt;/li&gt;
&lt;li&gt;Episodic: memory to specific event in time.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Episodic memory is what really interests me and especially how to improve it but semantic memory has been a bit of an obsession of mine for a long time too, so I'd like to investigate both of these mediums.&lt;/p&gt;
&lt;h1 id="improving-episodic-memory"&gt;Improving Episodic Memory&lt;/h1&gt;
&lt;p&gt;According to bunch of resources there are ways to improve episodic memories but nothing is really certain so I've designed myself few episodic memory improvement strategies that I'd like to follow through this year.&lt;/p&gt;
&lt;p&gt;&lt;video autoplay="" height="240" loop="" muted="" title="It seems that sometimes brain needs a bit of hand to start going!" width="480"&gt;&lt;source src="/gifs/brain-hand.mp4" type="video/mp4"/&gt;&lt;/video&gt;&lt;figcaption&gt;It seems that sometimes brain needs a bit of hand to start going!&lt;/figcaption&gt;&lt;/p&gt;
&lt;h2 id="emotion"&gt;Emotion&lt;/h2&gt;
&lt;p&gt;Generally memories with emotion attach to them tend to be much stronger. Obviously you can't really fake emotions to improve retention of a memory, but there are few ways you &lt;em&gt;could take advantage&lt;/em&gt; of this fact.  &lt;/p&gt;
&lt;p&gt;One particular idea of flashbulb memory indicates that attaching feeling of scale and importance helps to retain a more vivid memory:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A &lt;strong&gt;flashbulb memory&lt;/strong&gt; is a memory laid down in great detail during a highly personally significant event, often a shocking event of national or international importance.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Taking advantage of this idea in this context is a bit hard but I've noticed that merely acknowledging emotions attached to memories helps to strengthen episodic memories as well as make them just more pleasant. For example a memory at the beach - I was happy and dazzled by the scenic view but the abundance of jelly fish made me feel a bit anxious. Just acknowledging these emotions makes the memory more vivid, it's as if the emotions helps the memory to branch out to deeper parts of me.&lt;/p&gt;
&lt;p&gt;For this section the &lt;strong&gt;resolution&lt;/strong&gt; is quite simple - &lt;strong&gt;acknowledge the emotions attached to memories&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="drugs"&gt;Drugs&lt;/h2&gt;
&lt;p&gt;The relationship between memory and drugs has always been quite conflicting but there are some clear evidence that drugs can improve it. &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A 2015 meta-analysis of high quality evidence found that therapeutic doses of &lt;strong&gt;amphetamine and methylphenidate&lt;/strong&gt; improve performance on working memory, episodic memory, and inhibitory control tests in normal healthy adults.[1]&lt;br/&gt;
[1] - https://repository.upenn.edu/neuroethics_pubs/130&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Unfortunately most of episodic memory enhancing drugs are quite hardcore and unsurprisingly are developed to fight against various serious diseases like Alzheimer's or Parkinson's. I've tried digging around but every episodic memory drug seems to be quite hardcore messing of proteins and neurons which means any sort of recreational or supplemental usage is not worth it.&lt;/p&gt;
&lt;p&gt;However malnutrition is clearly attributed to episodic memory (and memory in general) quality. Clearly not many people reading this blog are not suffering from malnutrition, right? Well I've been doing yearly blood tests and taking along everyone I could talk into it with me and you'd be surprising - almost everyone &lt;em&gt;was&lt;/em&gt; missing something. Personally my last years test was low on iron, B12 and vitamin D - I could clearly see my memory improve after sorting this out within few months.&lt;/p&gt;
&lt;p&gt;My &lt;strong&gt;resolution&lt;/strong&gt; for this section is simply to &lt;strong&gt;take care of nutrition&lt;/strong&gt; better - more varied diet, some supplements and general awareness that the biochemistry in my body is important for my self.&lt;/p&gt;
&lt;h2 id="recall"&gt;Recall&lt;/h2&gt;
&lt;p&gt;By far the most common way of strengthening episodic memories is recalling the memories and everything attached to them. Basically the more you remember the memory the more it gets stuck in your head.&lt;/p&gt;
&lt;p&gt;I'll be focusing on this the most in my approach - designing exercises to efficiently and accurately recall memories seems like the most effective improvement strategy.&lt;/p&gt;
&lt;p&gt;Being a software nerd that I am I'll start my subject with a bunch of software tools that can help. &lt;/p&gt;
&lt;h3 id="monica-personal-contact-assistant"&gt;Monica - Personal Contact Assistant&lt;/h3&gt;
&lt;p&gt;First I'd like to start out with &lt;a href="https://www.monicahq.com/"&gt;Monica&lt;/a&gt; - a personal assistant web app where you can create contacts and keep track of experiences and events.&lt;/p&gt;
&lt;p&gt;&lt;a href="/images/monicahq.png"&gt;&lt;img class="bigc" loading="lazy" src="/images/monicahq.png" title="it's like a digital rolodex!"/&gt;&lt;/a&gt;&lt;figcaption&gt;it's like a digital rolodex!&lt;/figcaption&gt;&lt;/p&gt;
&lt;p&gt;This tool is not only great for recalling memories but for managing contacts as well. It helps you to be a better friend, coworker or even a family member - historical information makes it easier to care!&lt;br/&gt;
I've already been using Monica a bit but my goal here is to incorporate it even more into my routines.&lt;/p&gt;
&lt;h3 id="photography-and-video"&gt;Photography and Video&lt;/h3&gt;
&lt;p&gt;Second tool so to speak is simply taking more photos. I love photography and there's an immense memory capture value here. Just scrolling through &lt;strong&gt;instagram stories&lt;/strong&gt; or &lt;strong&gt;snapchat&lt;/strong&gt; archives makes for a really fun and fulfilling experiences. Needless to say capturing visual moments is a brilliant recalling tool and I want to do more of it. &lt;/p&gt;
&lt;p&gt;&lt;video autoplay="" height="240" loop="" muted="" title="Take pictures of everything and sort it later!" width="480"&gt;&lt;source src="/gifs/camera-much.mp4" type="video/mp4"/&gt;&lt;/video&gt;&lt;figcaption&gt;Take pictures of everything and sort it later!&lt;/figcaption&gt;&lt;/p&gt;
&lt;p&gt;My currently tool usage for this medium is as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.instagram.com/wraptile_"&gt;Instagram&lt;/a&gt; Feed - feed of my own photos, it's more of a collage or a CV of my past self to my future self.  &lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.instagram.com/wraptile_"&gt;Instagram&lt;/a&gt; Stories and Snapchat Archives - feed of my activities, snippets of various events and adventures.  &lt;/li&gt;
&lt;li&gt;Photography on &lt;a href="https://pixelfed.social/Wraptile"&gt;Pixelfed&lt;/a&gt; - most interesting photography experiences go here.&lt;/li&gt;
&lt;li&gt;Photos and Videos - Feed of my bigger events. I like to edit videos and photos but it's rather time consuming so I'm reserving bigger events.  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For storing photos and videos on cloud the biggest challenge  is actually curating the media to make recalling a streamlined and pleasant experience. &lt;br/&gt;
For that I'm using &lt;a href="http://pcloud.com/"&gt;Pcloud&lt;/a&gt; cloud storage and dedicate few minutes every weekend to clean up auto-uploads for a more pleasant recalling experience if I do want to go back and look through my photos. &lt;br/&gt;
There are applications which help to curate your feeds by generating stories of what it thinks are most important bits - this helps immensely if you don't curate your albums by hand. I haven't researched this thoroughly enough so one of my resolution goals is to categorize my photos better. For that I'll be investing more time with &lt;a href="https://www.darktable.org/"&gt;Darktable&lt;/a&gt; 3.0 release and go from there.&lt;/p&gt;
&lt;h3 id="journal"&gt;Journal&lt;/h3&gt;
&lt;p&gt;An obvious way to strengthen recalling is to start a journal (aka diary or log book). Not only it can be therapeutic way to relieve your mind and organize your thoughts but it's a great tool for improving memory too. Recalling memories right after they happened is a sure way to strengthen them, to add you can easily revisit these memories and recall them at any time given that your logbook system is properly indexed.&lt;/p&gt;
&lt;p&gt;&lt;video autoplay="" height="240" loop="" muted="" title="Is making a journal terminal with raspberry pi going too far?" width="480"&gt;&lt;source src="/gifs/fallout-terminal.mp4" type="video/mp4"/&gt;&lt;/video&gt;&lt;figcaption&gt;Is making a journal terminal with raspberry pi going too far?&lt;/figcaption&gt;&lt;/p&gt;
&lt;p&gt;I love static site generators and &lt;a href="https://github.com/ge3tpelican/pelican"&gt;pelican&lt;/a&gt; in particular thus I've started my log book as a local static website. Pelican allows me to categorise my entries by tags, date and any other additional meta fields allowing easy recall to any specific entries.&lt;/p&gt;
&lt;p&gt;&lt;a href="/images/pelican-journal.png"&gt;&lt;img class="bigc" loading="lazy" src="/images/pelican-journal.png" title="html journal with pelican"/&gt;&lt;/a&gt;&lt;figcaption&gt;html journal with pelican&lt;/figcaption&gt;&lt;/p&gt;
&lt;p&gt;For this I've developed &lt;a href="https://github.com/Granitosaurus/pelican-journal"&gt;pelican-journal&lt;/a&gt; - a pre-made pelican project designed for tag-based journaling.&lt;/p&gt;
&lt;h2 id="exercise-meditation-and-fasting_1"&gt;Exercise, Meditation and Fasting&lt;/h2&gt;
&lt;p&gt;There a sufficient amount of evidence that exercise, meditation and even occasional fasting helps to improve memory. However I've struggling integrating exercise into my routine.   &lt;/p&gt;
&lt;p&gt;&lt;video autoplay="" height="240" loop="" muted="" title="" width="480"&gt;&lt;source src="/gifs/spongebob-workingout.mp4" type="video/mp4"/&gt;&lt;/video&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;/p&gt;
&lt;p&gt;Often I find exercise exciting at first but lately it has been failing to enter my routine. I identified few causes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Lack of visible progression. 
This is a bit difficult to address as the progression tends to slow down after initial two months or so during which you can upgrade your weights every few visits. After a while, however, I feel that I'm hitting a barrier of X kilos and lose interest. &lt;br/&gt;
For that my resolution is tracking the progression in notes: weight, diameters and strength.&lt;/li&gt;
&lt;li&gt;Pushing for too long gym visits.&lt;br/&gt;
I've been a bit of a &lt;em&gt;victim&lt;/em&gt; of peer pressure to stay in gym longer than I really want to which really just kills my interest. I find 45 minutes to be a sweet spot for me. &lt;br/&gt;
My resolution in this case is to push myself less.&lt;/li&gt;
&lt;li&gt;Timing.&lt;br/&gt;
This has been the hardest to figure out. When to go to gym? Sometimes I enjoy mornings, sometimes I enjoy evening but constant mixing of gym times messes up my routine.&lt;br/&gt;
For this I've decided to focus on 4-5 o'clock. Hit the gym for an hour and just in time to catch early Thai sunset for after-gym recreation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For mediatation but I've been struggling to find enjoyment in it. I usually pick it up for few weeks and then lose interest. I've tried apps like &lt;a href="https://www.headspace.com/"&gt;headspace&lt;/a&gt; without much luck. However it only takes 5-10 minutes, I really need to give it a proper go.&lt;/p&gt;
&lt;h1 id="summary-and-further-research_1"&gt;Summary and Further Research&lt;/h1&gt;
&lt;p&gt;For 2020 I've decided to strengthen my episodic memory through: taking more pictures and organizing them better, keeping track of contacts and events using Monica assistant and keep writing daily or at least semi-daily journal.&lt;/p&gt;
&lt;p&gt;There seems to be a lot of information on the internet and I'm still on the lookout for possible improvement strategies but so far I'm satisfied with few approaches I've described here. 
For further improvement I have my eyes on &lt;a href="https://www.magneticmemorymethod.com/"&gt;magnetic memory method&lt;/a&gt; - seems like memory course but the author seems to be quite legit their &lt;a href="https://www.magneticmemorymethod.com/episodic-memory/"&gt;article on episodic memory&lt;/a&gt; was surprisingly well writen, though I'm not very trustworthy of self-help salesmen and the name "magnetic" doesn't give much credibility but based on that article it might be worth investigating further.&lt;/p&gt;
&lt;h1 id="happy-2020"&gt;Happy 2020&lt;/h1&gt;
&lt;p&gt;&lt;video autoplay="" height="240" loop="" muted="" title="" width="480"&gt;&lt;source src="/gifs/2020.mp4" type="video/mp4"/&gt;&lt;/video&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;/p&gt;
&lt;p&gt;Whoever read this, enjoy the new year! Hopefully you found some useful material in this article to make this year better :)&lt;/p&gt;</content><category term="self"></category><category term="self"></category><category term="memory"></category></entry><entry><title>Get Organized With TaskWarrior!</title><link href="https://granitosaur.us/taskwarrior-intro.html" rel="alternate"></link><published>2019-10-26T00:00:00+02:00</published><updated>2019-10-26T00:00:00+02:00</updated><author><name>Bernardas Ališauskas</name></author><id>tag:granitosaur.us,2019-10-26:/taskwarrior-intro.html</id><summary type="html">&lt;p&gt;Quick introduction how to get productive and organized with amazing commandline tool called Taskwarrior!&lt;/p&gt;</summary><content type="html">&lt;p&gt;If you've been part of terminal ecosystem for you're most likely familiar with numerous terminal &lt;em&gt;todo&lt;/em&gt; sort of programs. I'd like to introduce you to king of all of them &lt;a href="https://taskwarrior.org/"&gt;Taskwarrior&lt;/a&gt;. &lt;br/&gt;
The name might sound intimidating but don't go - it's quite the opposite: it's butter smooth!  &lt;/p&gt;
&lt;h1 id="intro"&gt;Intro&lt;/h1&gt;
&lt;p&gt;Your key command here is &lt;code&gt;task&lt;/code&gt;. Typing this alone will list your open tasks:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ task

ID Age  Description                    Urg 
&lt;span class="m"&gt;1&lt;/span&gt; 32s   write a blog about taskwarrior    &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="m"&gt;2&lt;/span&gt; 11s   make carrot cake                  &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="m"&gt;3&lt;/span&gt; 2s    pay electric bills                &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then the rest of cli goes as intuitive as it follows, these are your 3 vital beginner commands:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;add "some task"&lt;/code&gt; - to add a task.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;done &amp;lt;id&amp;gt;&lt;/code&gt; - to close task.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;modify &amp;lt;id&amp;gt;&lt;/code&gt; - change something about task.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Finally Taskwarrior has one vital important feature for managing your tasks - &lt;strong&gt;tags&lt;/strong&gt;. You simply specify tags with &lt;code&gt;+&lt;/code&gt; signs in front of your commands:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ task +work add &lt;span class="s2"&gt;"fix linkedin.com crawler"&lt;/span&gt;
$ task +work 
ID Age  Description                    Urg 
&lt;span class="m"&gt;1&lt;/span&gt;  2s   fix linkedin.com crawler        &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can see already with these 4 little things: add, list, modify, tags - you have great powers. Time for example workflow!&lt;/p&gt;
&lt;h1 id="example-workflow"&gt;Example Workflow&lt;/h1&gt;
&lt;p&gt;This is a simple version of my taskwarrior workflow; First add tasks the moment you get them and try to split them up into small tasks - you're not an idiot you won't get confused don't worry:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ task add +work &lt;span class="s2"&gt;"fix project1"&lt;/span&gt;
Created task &lt;span class="m"&gt;1&lt;/span&gt;.

$ task add +work &lt;span class="s2"&gt;"msg admin about project1 dump"&lt;/span&gt;
Created task &lt;span class="m"&gt;2&lt;/span&gt;.

$ task add +shop &lt;span class="s2"&gt;"potatoes - for potato cannon"&lt;/span&gt;
Created task &lt;span class="m"&gt;3&lt;/span&gt;.

$ task add +me &lt;span class="s2"&gt;"1 chapter of new book"&lt;/span&gt;
Created task &lt;span class="m"&gt;4&lt;/span&gt;.

$ task add +me &lt;span class="s2"&gt;"wash dishes"&lt;/span&gt;
Created task &lt;span class="m"&gt;5&lt;/span&gt;.

$ task add +gf &lt;span class="s2"&gt;"research christmas present on etsy"&lt;/span&gt;
Created task &lt;span class="m"&gt;6&lt;/span&gt;.

$ task add +me &lt;span class="s2"&gt;"practice bass a bit"&lt;/span&gt;
Created task &lt;span class="m"&gt;7&lt;/span&gt;.

$ task
ID Age  Tag  Description                        Urg 
&lt;span class="m"&gt;1&lt;/span&gt; 7min work fix project1                        &lt;span class="m"&gt;0&lt;/span&gt;.8
&lt;span class="m"&gt;2&lt;/span&gt; 6min work msg admin about project1 dump       &lt;span class="m"&gt;0&lt;/span&gt;.8
&lt;span class="m"&gt;3&lt;/span&gt; 6min shop potatoes - &lt;span class="k"&gt;for&lt;/span&gt; potato cannon        &lt;span class="m"&gt;0&lt;/span&gt;.8
&lt;span class="m"&gt;4&lt;/span&gt; 5min me   &lt;span class="m"&gt;1&lt;/span&gt; chapter of new book               &lt;span class="m"&gt;0&lt;/span&gt;.8
&lt;span class="m"&gt;5&lt;/span&gt; 5min me   wash dishes                         &lt;span class="m"&gt;0&lt;/span&gt;.8
&lt;span class="m"&gt;6&lt;/span&gt; 4min gf   research christmas present on etsy  &lt;span class="m"&gt;0&lt;/span&gt;.8
&lt;span class="m"&gt;7&lt;/span&gt; 4min me   practice bass a bit                 &lt;span class="m"&gt;0&lt;/span&gt;.8 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Right, got bunch of tasks and now just pop them as you feel like. Having a weekday work day? &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ task +work
ID Age   Tag  Description                   Urg 
&lt;span class="m"&gt;1&lt;/span&gt; 11min work fix project1                   &lt;span class="m"&gt;0&lt;/span&gt;.8
&lt;span class="m"&gt;2&lt;/span&gt; 10min work msg admin about project1 dump  &lt;span class="m"&gt;0&lt;/span&gt;.8
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Task 1 really needs to get done: &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ task start &lt;span class="m"&gt;1&lt;/span&gt;
Starting task &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="s1"&gt;'fix project1'&lt;/span&gt;.
Started &lt;span class="m"&gt;1&lt;/span&gt; task.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;All this does is mark the task in your list that it's "ongoing"&lt;/em&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ task +work
ID Active Age   Tag  Description                   Urg 
 &lt;span class="m"&gt;1&lt;/span&gt; 1min   14min work fix project1                   &lt;span class="m"&gt;4&lt;/span&gt;.8
 &lt;span class="m"&gt;2&lt;/span&gt;        13min work msg admin about project1 dump  &lt;span class="m"&gt;0&lt;/span&gt;.8
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Been working hard for an hour or so, time for a short break. Lets keep the task active so we could jump right back in after our break and take a look at &lt;code&gt;me&lt;/code&gt; tasks we have for our break:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ task +me 
ID Age   Tag Description           Urg 
 &lt;span class="m"&gt;4&lt;/span&gt; 14min me  &lt;span class="m"&gt;1&lt;/span&gt; chapter of new book  &lt;span class="m"&gt;0&lt;/span&gt;.8    
 &lt;span class="m"&gt;5&lt;/span&gt; 14min me  wash dishes            &lt;span class="m"&gt;0&lt;/span&gt;.8
 &lt;span class="m"&gt;7&lt;/span&gt; 12min me  practice bass a bit    &lt;span class="m"&gt;0&lt;/span&gt;.8
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Great time to boot up the bass for a song or two and while &lt;em&gt;Rocksmith on ps4&lt;/em&gt; is booting up - wash the dishes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ task &lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt;
Completed task &lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="s1"&gt;'wash dishes'&lt;/span&gt;.
Completed &lt;span class="m"&gt;1&lt;/span&gt; task.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Break over, simply jump back to work, the task you've been working on is still glowing red there waiting for you ;)&lt;/p&gt;
&lt;h1 id="get-micro-motivated"&gt;Get Micro Motivated!&lt;/h1&gt;
&lt;p&gt;Taskwarrior allows this sort of dead-simple micro-motivation. It's so simple an intuitive that in never feels like &lt;em&gt;additional&lt;/em&gt; work.&lt;/p&gt;
&lt;p&gt;Often it's easy to go through your day without having tangible experience of your accomplishments. No matter how small your tasks are - the day feels so much better when you can visualize and feel the tiny hills you've conquered!&lt;/p&gt;
&lt;h1 id="further-reading"&gt;Further Reading&lt;/h1&gt;
&lt;p&gt;Taskwarrior also packs a bunch of awesome, rich features but those are beyond the scope of this blog post.&lt;br/&gt;
Taskwarrior documentation is well written and quite short:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;intro: https://taskwarrior.org/docs/introduction.html&lt;/li&gt;
&lt;li&gt;30 second tutorial: https://taskwarrior.org/docs/30second.html&lt;/li&gt;
&lt;li&gt;example workflows of users: https://taskwarrior.org/docs/workflow.html&lt;/li&gt;
&lt;li&gt;&lt;code&gt;man task&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Bugwarrior plugin for pulling issues from other sources - https://bugwarrior.readthedocs.io/&lt;/li&gt;
&lt;/ul&gt;</content><category term="self"></category><category term="productivity"></category><category term="linux"></category></entry><entry><title>1 Year in Chiang Mai, Thailand</title><link href="https://granitosaur.us/chiang-mai-1.html" rel="alternate"></link><published>2019-06-02T00:00:00+02:00</published><updated>2019-06-02T00:00:00+02:00</updated><author><name>Bernardas Ališauskas</name></author><id>tag:granitosaur.us,2019-06-02:/chiang-mai-1.html</id><summary type="html">&lt;p&gt;My experience of living 1 year in Chiang Mai, Thailand for 1 year now.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've been living in Chiang Mai - biggest city in North Thailand - for over one year now. I did a lot during this year and experienced many things. The food is great, the housing is cool and the language is complicated. Let me tell you all about it! &lt;/p&gt;
&lt;h1 id="visa-and-getting-here"&gt;Visa And Getting Here&lt;/h1&gt;
&lt;p&gt;Getting to Chiang Mai is dead easy - take a flight to Bangkok and then another one straight to Chiang Mai! There are flights almost every hour and are surprisingly cheap. The flight from Europe (e.g. Riga) - Bangkok is around 350&amp;euro; + Bangkok - Chiang Mai 50&amp;euro; = &lt;strong&gt;~400&amp;euro;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;For Visa it's a bit more complicated. There are several types of visa but the most interesting ones:&lt;/p&gt;
&lt;h2 id="tourist-visa"&gt;Tourist Visa&lt;/h2&gt;
&lt;p&gt;Anyone can get this visa. You can either get 30 days on arrival visa or submit your passport with ~50&amp;euro; and get a 60 days visa with a possibility of 30 days extension for another ~50&amp;euro;.&lt;/p&gt;
&lt;p&gt;There's one thing to note though. For tourist visa you need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Onwards Ticket - some transportation ticket out of the country&lt;/li&gt;
&lt;li&gt;Financial Proof - that you have some money (bank statement or cash)&lt;/li&gt;
&lt;li&gt;Hotel Reservation/Address - this one is rather weird as you can just enter random hotel address.  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="/images/tourist-to-officer.png"&gt;&lt;img class="bigc" loading="lazy" src="/images/tourist-to-officer.png" title="You really don't want to mess with a guy that spends 8 hours a day in a chair stamping documents!"/&gt;&lt;/a&gt;&lt;figcaption&gt;You really don't want to mess with a guy that spends 8 hours a day in a chair stamping documents!&lt;/figcaption&gt;&lt;/p&gt;
&lt;p&gt;I've started with this visa one year ago by taking my passport to Thai embassy in Hanoi and getting it back with a visa next day -&amp;gt; easy peasy!&lt;/p&gt;
&lt;h2 id="education-visa"&gt;Education Visa&lt;/h2&gt;
&lt;p&gt;This is visa you can get by applying to study something. This can be almost anything but usually it's either Thai language or self-defense training like Thai boxing (Muai-Thai).&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is actually very cool idea as it setup this little education infrastructure for foreigners. You can stay here as long as you study something of ours! Pretty awesome!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;There are either proper schools or "visa farms" that really don't do any real teaching and only provide you documents to get your visa.&lt;/p&gt;
&lt;p&gt;The great bit about student visas that they are kinda "eternal" (some suggest that after 3-4 years you might start to be dnied) - as long as you are studying you can extend your visa for 90 days for ~50&amp;euro;. It only takes few hours of your day to get it done and you're set!  &lt;/p&gt;
&lt;h2 id="getting-my-education-visa"&gt;Getting My Education Visa&lt;/h2&gt;
&lt;p&gt;I've picked up the best rated school in Chiang Mai called &lt;a href="http://manathaischool.co.th"&gt;Mana Thai School&lt;/a&gt;, dropped &lt;strong&gt;~750&amp;euro; for a yearly course&lt;/strong&gt; which involved 2 hour lessons 4-times a week and found out a rather bizarre initial process.&lt;/p&gt;
&lt;p&gt;With school contract in hand and tourist visa in my passport I found out that to acquire student visa I need to leave Thailand and apply for it in Thai embassy - here started my unexpected travel to Laos!&lt;/p&gt;
&lt;p&gt;&lt;a href="/images/chiang-mai_to_vientiane.png"&gt;&lt;img class="bigc" loading="lazy" src="/images/chiang-mai_to_vientiane.png" title="flight-&amp;gt;bus-&amp;gt;tuk tuk-&amp;gt;bus-&amp;gt;taxi-&amp;gt;flight"/&gt;&lt;/a&gt;&lt;figcaption&gt;flight-&amp;gt;bus-&amp;gt;tuk tuk-&amp;gt;bus-&amp;gt;taxi-&amp;gt;flight&lt;/figcaption&gt;&lt;/p&gt;
&lt;p&gt;This is called visa run - you leave the country to apply for a new tourist/education visa. The most common one is going to Thai embassy in capital of Laos - Vientianne and it's awkward, stressful and surprisingly expensive journey.&lt;/p&gt;
&lt;p&gt;It took me two flights, couple of bus rides and one expensive taxi ride to not miss my flight back. I also got ripped off at the border by being required to buy visa because the workers didn't know what Lithuania was. &lt;/p&gt;
&lt;p&gt;&lt;a href="/images/vientiane-morning.jpg"&gt;&lt;img class="bigc" loading="lazy" src="/images/vientiane-morning.jpg" title="waiting for the wrong embassy to open"/&gt;&lt;/a&gt;&lt;figcaption&gt;waiting for the wrong embassy to open&lt;/figcaption&gt;&lt;/p&gt;
&lt;p&gt;Finally I went to the wrong embassy early in the morning, so when I finally got to the real one I spent 8 or so hours in a queue to get my passport and request to study submitted.&lt;/p&gt;
&lt;p&gt;&lt;a href="/images/vientiane-day.jpg"&gt;&lt;img class="bigc" loading="lazy" src="/images/vientiane-day.jpg" title="there is some interesting sight-seeing to do!"/&gt;&lt;/a&gt;&lt;figcaption&gt;there is some interesting sight-seeing to do!&lt;/figcaption&gt;&lt;/p&gt;
&lt;p&gt;For the rest of the day I explored Vientiane which was quite tiny but cosy. It's was a pleasant mix of Thailand and Vietnam but not as exciting, busy or captivating like either. After exploring Pagodas, historic buildings, parks and markets I had enough but I'd definitely would come back to spend there a week or so.  &lt;/p&gt;
&lt;h1 id="studying-thai_1"&gt;Studying Thai&lt;/h1&gt;
&lt;p&gt;I've spent almost a whole year learning Thai and it was quite fun. Thai is a very unusual language for my background - it's my first tonal language and it's quite a barrier!  &lt;/p&gt;
&lt;p&gt;I've been studying 2 hours a day for 4 days a week in a group of 5-10 people at &lt;a href="http://manathaischool.co.th"&gt;Manna Thai School&lt;/a&gt;. The first two months we learned just plain talking and some writing in latin'ish alphabet - it was great fun. After writing and reading started and things got much more complicated quickly.&lt;/p&gt;
&lt;p&gt;&lt;a href="/images/thai-alphabet.jpg"&gt;&lt;img class="bigc" loading="lazy" src="/images/thai-alphabet.jpg" title="every letter has an official word attached to it. Like Z for Zebra but standartized which is really great for learning!"/&gt;&lt;/a&gt;&lt;figcaption&gt;every letter has an official word attached to it. Like Z for Zebra but standartized which is really great for learning!&lt;/figcaption&gt;&lt;/p&gt;
&lt;p&gt;Thai writing system is very unique and fun but also very dated and filled with old rules, exceptions and dead letters. Some interesting facts about Thai language:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Thai language is a &lt;strong&gt;tonal language&lt;/strong&gt; and has 4 different tones, which means your tone can be the difference between a horse or a dog or between number two and a brothel. Tones are really hard to as rules are complicated and even most native thai speakers don't know them and just talk by ear.  &lt;/li&gt;
&lt;li&gt;There are &lt;strong&gt;46 consonant letters&lt;/strong&gt; in Thai but only 21 consonant sounds, which means you have multiple letters for same consonant and they are not interchangeable, i.e. you have to use correct letter T (there are 6 T letters) from in word Turtle or you might end up writing something else entirely.  &lt;/li&gt;
&lt;li&gt;There are &lt;strong&gt;dead letters&lt;/strong&gt; that are either completely dead or used in very few words.  &lt;/li&gt;
&lt;li&gt;Thai &lt;strong&gt;vowels can go in any direction&lt;/strong&gt;: left, right, up, bottom! Unlike most languages that simple go from left to right in order thai is very peculiar and instead you write consonant and attach 1 of many different vowels that have fixed location around the consonant (e.g. long U goes under a consonant while short a goes above it)  &lt;/li&gt;
&lt;li&gt;There are &lt;strong&gt;no spaces between words&lt;/strong&gt; in written thai which makes reading as a beginner really difficult.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="/images/thai-love-you.png"&gt;&lt;img "pom="" (from="" class="bigc" i="" in="" kun"="" like="" loading="lazy" love="" male's="" perspective)"="" rak="" something="" sounds="" src="/images/thai-love-you.png" thai,="" title="Words " writen="" you"=""/&gt;&lt;/a&gt;&lt;figcaption&gt;Words "I love you" writen in Thai, sounds something like "pom rak kun" (from male's perspective)&lt;/figcaption&gt;&lt;/p&gt;
&lt;p&gt;All that being said it's a fun language and I enjoy learning it, it's just not a very good one and definitely in need of some reforms. Neighbouring countries of Laos and Myanmar use similar languages with few reforms and they are known to be much more sane.&lt;/p&gt;
&lt;p&gt;&lt;a href="/images/mana-400h.png"&gt;&lt;img class="bigc" loading="lazy" src="/images/mana-400h.png" title="It's a great deal; 400 hours definitely seems a lot!"/&gt;&lt;/a&gt;&lt;figcaption&gt;It's a great deal; 400 hours definitely seems a lot!&lt;/figcaption&gt;&lt;/p&gt;
&lt;p&gt;On paper I did 400 hours of classes but I'll be honest - I've been skipping a few classes. That being said I got quite a bit done. I'm quite satisfied with my current elementary-school level writing/reading and some basic talking/listening. I think I can pick it up to a rather fluent level in another year. according to &lt;a href="https://blog.thelinguist.com/how-long-should-it-take-to-learn-a-language"&gt;FSI source&lt;/a&gt; to acquire &lt;strong&gt;basic fluency&lt;/strong&gt; in Thai you need around &lt;strong&gt;720 hours&lt;/strong&gt;. So I'm trying not to beat myself up too much.&lt;/p&gt;
&lt;p&gt;Main problem is actually practicing Thai. It's surprisingly difficult to immerse yourself in the language if you don't actively put effort into it. I've been working on extending this with some training apps and tv shows on Netflix.&lt;/p&gt;
&lt;p&gt;Overall Thai language is fun and I'm glad I picked it up. It's unusual writing system, tonal pronunciation and word order is a great mind exercise!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I'm in the works of writing a blog about all thai language learning tools I've tried, there are few good ones!&lt;/em&gt;&lt;/p&gt;
&lt;h1 id="lodging"&gt;Lodging&lt;/h1&gt;
&lt;p&gt;In this year I lived in three different condos and all of them were great value! The quality condo pricing ranges from &lt;strong&gt;270 - 550&amp;euro;&lt;/strong&gt;. If you want nice location, pool, gym, rec space(like library or park) and a bit more space then 500&amp;euro; with a yearly contract is a fair number - and that's an absolute steal!  &lt;/p&gt;
&lt;p&gt;All three condos I lived in all had pools, great staff, security and great location! &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="todo"&gt;Prompt Condo&lt;/a&gt; - had great location was cosy but not very luxurious - 420&amp;euro;/month. It has a cosy pool on ground floor and a little gym right above it. No shared space like library or a garden though.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="/images/prompt-condo.jpg"&gt;&lt;img class="bigc" loading="lazy" src="/images/prompt-condo.jpg" title="pool and a gym on second floor. It's quite cosy"/&gt;&lt;/a&gt;&lt;figcaption&gt;pool and a gym on second floor. It's quite cosy&lt;/figcaption&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="todo"&gt;Play Condo&lt;/a&gt; - my favorite experience in Chiang Mai. It's not very big but the staff was always helpful, the room for 560&amp;euro; was pretty huge (50m&amp;sup2; or so) and the rooftop pool and library is a real pleasure. The location was close to perfect too - across the street of night market and a super mall.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="/images/play-condo.jpg"&gt;&lt;img class="bigc" loading="lazy" src="/images/play-condo.jpg" title="rainy day near the pool is perfect for relaxing!"/&gt;&lt;/a&gt;&lt;figcaption&gt;rainy day near the pool is perfect for relaxing!&lt;/figcaption&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="todo"&gt;Escent Condo&lt;/a&gt; - I had to move to a different town area and found this new condo that is 26 stories high and is pretty much empty. For 460&amp;euro; you get around 40m&amp;sup2; of living space with nice view plus a rooftop pool/sauna, libary and gym. It's right next to huge super mall and few local markets too. The only down-side is that it's a bit far from a city center.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="/images/escent-condo.jpg"&gt;&lt;img class="bigc" loading="lazy" src="/images/escent-condo.jpg" title="26th floor really delivers a nice sunset!"/&gt;&lt;/a&gt;&lt;figcaption&gt;26th floor really delivers a nice sunset!&lt;/figcaption&gt;&lt;/p&gt;
&lt;p&gt;I found the condominium infrastructure very impressive and enjoyable. I'd have trouble going back to european style flats and for this price it's worth staying here just for the housing!&lt;/p&gt;
&lt;p&gt;&lt;a href="/images/cosy-condo.jpg"&gt;&lt;img class="bigc" loading="lazy" src="/images/cosy-condo.jpg" title="a perfect spot for an afternoon nap!"/&gt;&lt;/a&gt;&lt;figcaption&gt;a perfect spot for an afternoon nap!&lt;/figcaption&gt;&lt;/p&gt;
&lt;p&gt;The general cost is very affordable. You can find similar condos in 300&amp;euro; range too if you're more flexible with location. It's worth noting that electricity bills can add up really fast so you should add 30&amp;euro; or so for air-conditioning alone!&lt;/p&gt;
&lt;h1 id="food"&gt;Food&lt;/h1&gt;
&lt;p&gt;This segment probably deserves a blog post of it's own but to put it shortly thai food is great and surprisingly supportive of vegetarian diet, though egg is very prevalent so vegan diet is a bit harder.  &lt;/p&gt;
&lt;p&gt;Thai food is very rich in taste, be it salty, sweet or sour. It's relative healthy too - maybe a bit heavy on saturated fats (oil) and carbs (rice) but if you can pay a bit attention you can have a very healthy vegetarian diet without much effort!&lt;/p&gt;
&lt;p&gt;&lt;a href="/images/cheap_food.jpg"&gt;&lt;img class="bigc" loading="lazy" src="/images/cheap_food.jpg" title="7&amp;euro; for two days worth of delicious, vegetarian food!"/&gt;&lt;/a&gt;&lt;figcaption&gt;7&amp;euro; for two days worth of delicious, vegetarian food!&lt;/figcaption&gt;&lt;/p&gt;
&lt;p&gt;The biggest appeal is sheer value. I often take a backpack full of plastic containers and fill them up with various dishes at a local vegetarian restaurant. For &lt;strong&gt;8&amp;euro;&lt;/strong&gt; and can eat quality, delicious and somewhat healthy food &lt;strong&gt;for 2-3 days&lt;/strong&gt;! The best part is that it saves a bunch of time too!&lt;/p&gt;
&lt;p&gt;Lastly &lt;strong&gt;being vegetarian in Thailand isn't hard at all&lt;/strong&gt; but some effort is required. Thai people really like to eat meat so they tend to mix some meat into every dish which is hard to avoid unless you can communicate with staff to adjust the dish for you which they don't mind doing. The word for vegetarian is &lt;strong&gt;เจ&lt;/strong&gt; (pronounced &lt;em&gt;j&amp;edot;&amp;edot;&lt;/em&gt;) which stand for buddhist vegetarian diet.   &lt;/p&gt;
&lt;p&gt;&lt;a href="/images/thai-jee.jpg"&gt;&lt;img class="bigc" loading="lazy" src="/images/thai-jee.jpg" title="signs used to indicated vegatrian (jee) restaurant"/&gt;&lt;/a&gt;&lt;figcaption&gt;signs used to indicated vegatrian (jee) restaurant&lt;/figcaption&gt;&lt;/p&gt;
&lt;p&gt;The easiest way to find some delicious, cheap vegetarian food is looking for this word (usually in yellow background) around Temples - the restaurants/cafeterias serve the best vegetarian thai food there!&lt;/p&gt;
&lt;h2 id="produce"&gt;Produce&lt;/h2&gt;
&lt;p&gt;Vegetables and fruits in Chiang Mai are highly seasonal, though mostly by price and quality not by availability. There are two seasons in Thailand - wet and dry but for plants it's a bit more complicated. There are these, as I'd describe, "surge" months where specific vegetable or fruit is really kicking off and you can just drown yourself in it. As of writing of this blog the Lichee season is starting and thai lichee are absolutely delicious!&lt;/p&gt;
&lt;p&gt;&lt;a href="/images/thai-market.jpg"&gt;&lt;img class="bigc" loading="lazy" src="/images/thai-market.jpg" title="Produce stalls are everywhere! [1]"/&gt;&lt;/a&gt;&lt;figcaption&gt;Produce stalls are everywhere! [1]&lt;/figcaption&gt;&lt;/p&gt;
&lt;p&gt;The local markets (which are everywhere) offer a lot of quality produce for pennies - especially if you go out a bit out of town or go to wholesale markets. Some produce is a bit harder to get by at times but can be found at bigger markets or at some import shop.&lt;/p&gt;
&lt;p&gt;One of my favorite produce related experience is that often local alleys have small stalls of good quality produce, so if you're in a sudden need you can just pop down from your condo and grab some carrots or whatever you're missing for that delicious soup your cooking!&lt;/p&gt;
&lt;p&gt;For other favorite experiences here's few of my favorite produce from top of my head: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mango - green mango is pleasantly sour and great snack dipped in sauce or spice mixes, while yellow mango is perfect sweetness on it's own.&lt;/li&gt;
&lt;li&gt;Baby mango - it's just young green mango but you can eat them like apples, they are quite sour and very refreshing. Usually people eat them with sweet tamarind sauce.  &lt;/li&gt;
&lt;li&gt;Lychee/Longan - there are many kinds, some sweeter some less so but all of them are delicious and super cheap when in season!    &lt;/li&gt;
&lt;li&gt;Guava - apple of thailand. Packs more vitamin c than a lemon!  &lt;/li&gt;
&lt;li&gt;Pineapple - Chiang Rai (a town north of CM) is probably the world capital of pineapples. There are so many kinds and many different tastes! The baby ones are especially delicious as they lack of protein that burns your mouth.  &lt;/li&gt;
&lt;li&gt;Mangosteen - these little guys just melt your mouth! They are quite seasonal and difficult to peel but are absolutely worth it!  &lt;/li&gt;
&lt;li&gt;Salads - if you're into salads CM got you covered, I'm eating Red Coral almost daily! There are also many salad bars and make your own salad restaurants like &lt;a href="https://www.google.com/maps/search/okaju+chiang+mai"&gt;Okaju&lt;/a&gt; and &lt;a href="https://www.google.com/maps/search/salad+concept+chiang+mai"&gt;Salad Concept&lt;/a&gt; where you can get bowl of delicious, dressed salad for 2&amp;euro;!&lt;/li&gt;
&lt;li&gt;Eggplants - there are lot of different eggplants and they're all delicious. If you know your way around a kitchen you can really go wild with these guys!&lt;/li&gt;
&lt;li&gt;Berries - there aren't many berries in Thailand but few that do exist are extremely delicious. Mulberries and Cape Gooseberries are to die for! Unfortunately just like everywhere berries here are highly seasonal too.&lt;/li&gt;
&lt;li&gt;Mushrooms - there are many different mushrooms in varying tastes but all of them are delicious! My favorites are just sauted or fried with butter and chilly powder. Eringi mushroom is my daily protein driver!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Another absolutely delicious use of produce is &lt;strong&gt;pickling&lt;/strong&gt;. You can find almost pickled anything - mango, tamarind, grapes and all sorts of plume-like tiny little fruits I don't even know the name of!&lt;/p&gt;
&lt;p&gt;&lt;a href="/images/thai-pickled.jpg"&gt;&lt;img class="bigc" loading="lazy" src="/images/thai-pickled.jpg" title="Add some chilly powder and sugal mix and you got yourself a perfect snack!"/&gt;&lt;/a&gt;&lt;figcaption&gt;Add some chilly powder and sugal mix and you got yourself a perfect snack!&lt;/figcaption&gt;&lt;/p&gt;
&lt;p&gt;All that beings said some produce is surprisingly disappointing. Tomato is my favourite fruit and thai tomatoes aren't great, they are never proper ripe and aren't as watery as imported ones. Another disappointing thing is serious lack of canned legumes, beans etc. - it's awfully expensive and not as great as you would get back in europe.&lt;/p&gt;
&lt;h2 id="weedsleafsflowers"&gt;Weeds/leafs/flowers&lt;/h2&gt;
&lt;p&gt;There are so many little obscure plants that Thai like to deep or stir fry or mix in some curry. I could hardly do it justice in this small section but variety is truly astonishing. &lt;br/&gt;
If you take a thai cook to a local jungle and ask them to start pointing at edible plants you'd be surprised to see them turn into a helicopter. &lt;br/&gt;
Stir fry pan is probably the ultimate survival tool for thai outdoors.&lt;/p&gt;
&lt;p&gt;The most basic but absolutely delicious dish is simple omelet, mixed in with some of these obscurities eaten with some chilly sauce and rice. It's has been an easy breakfast/lunch for when you want some &lt;em&gt;heavy&lt;/em&gt; food - pop in to veggie stall at your alley to get some plants and sticky rice, toss the plants in an omelet and combined that with some sauce and sticky rice you have a carb and fat rich, delicious, and most importantly - lazy, lunch!&lt;/p&gt;
&lt;p&gt;Another one of my favorite dishes called Nam Niau is a soup made with dried flower called Niau, literal dish translation &lt;em&gt;water Niau&lt;/em&gt; and it's delicious! usually combined with rice noodles, tomatoes and some extras like liver or in vegetarian versions mushrooms.&lt;/p&gt;
&lt;p&gt;&lt;a href="/images/thai-nam-niau.jpg"&gt;&lt;img class="bigc" loading="lazy" src="/images/thai-nam-niau.jpg" title="Niau flower soup is different everywhere I tried but the core soup is always as delicious as you expect it! [1]"/&gt;&lt;/a&gt;&lt;figcaption&gt;Niau flower soup is different everywhere I tried but the core soup is always as delicious as you expect it! [1]&lt;/figcaption&gt;&lt;/p&gt;
&lt;h2 id="coffee-and-tea"&gt;Coffee and Tea&lt;/h2&gt;
&lt;p&gt;Thailand is full of Tea and Coffee plantations and they grow some of the most delicious stuff in the world! Not only that but these plantations tend to be a fun place to visit and often have a little place to get some dinner with a view.&lt;/p&gt;
&lt;p&gt;&lt;a href="/images/cm_tea-plantation.jpg"&gt;&lt;img class="bigc" loading="lazy" src="/images/cm_tea-plantation.jpg" title="an hour drive to the mountains for breathtaking lunch over a tea plantation"/&gt;&lt;/a&gt;&lt;figcaption&gt;an hour drive to the mountains for breathtaking lunch over a tea plantation&lt;/figcaption&gt;&lt;/p&gt;
&lt;p&gt;Thailand really shines in this regard, some of the best Arabica coffee in the world can be found here in North Thailand. The mountains are just tall and accommodating enough to produce great quality beans that are further roasted and taken care of families and hill tribes that have been doing this for generations.&lt;/p&gt;
&lt;p&gt;The coffee is really cheap too if avoid the tourist brands. A 500g pack high quality arabica from a hill tribe (you can even find some in nearby super market) will only cost you 3&amp;euro;!&lt;/p&gt;
&lt;p&gt;The caf&amp;egrave; scene in Chiang Mai is very healthy too. There are a lot of little coffee shops sprinkled around even at the most remote corners of the country. All serving good, delicious coffee 1-3&amp;euro; and often provide an either themed or cosy to sit down to drink it.&lt;/p&gt;
&lt;p&gt;&lt;a href="/images/cm_geese-friends.jpg"&gt;&lt;img and="" art="" birds="" class="bigc" company"="" geese="" has="" keeping="" loading="lazy" other="" roastery"="" src="/images/cm_geese-friends.jpg" title="my favorite caf&amp;eacute; " you=""/&gt;&lt;/a&gt;&lt;figcaption&gt;my favorite caf&amp;eacute; "Art Roastery" has geese and other birds keeping you company&lt;/figcaption&gt;&lt;/p&gt;
&lt;p&gt;Since I work remotely I often do that from a caf&amp;eacute;. The best cafes in my experience are located towards Doi Suthep mountain, so if you enjoy caf&amp;eacute; hoping that's the direction you should aim at.&lt;/p&gt;
&lt;h1 id="the-smog_1"&gt;The Smog&lt;/h1&gt;
&lt;p&gt;So far most of the things I said about Chiang Mai were very much positive. It's absolutely true - Chiang Mai is wonderful for the bigger part of the year but starting around January &lt;strong&gt;burning season&lt;/strong&gt; starts and goes on until late May. It's terrible.&lt;/p&gt;
&lt;p&gt;&lt;a href="/images/cm_dirty-clean.jpg"&gt;&lt;img class="bigc" loading="lazy" src="/images/cm_dirty-clean.jpg" title="March compared to end of May"/&gt;&lt;/a&gt;&lt;figcaption&gt;March compared to end of May&lt;/figcaption&gt;&lt;/p&gt;
&lt;p&gt;It's ugly, but not only that it's just awfully unhealthy and the smog has a very unpleasant smell which makes any outdoors activity really unpleasant. Burning season is also &lt;em&gt;high&lt;/em&gt; season with more tourists coming from cold countries to enjoy Thai sunshine - means prices are often higher and the town is packed more than usual.   &lt;/p&gt;
&lt;p&gt;Combined these two things I couldn't recommend anyone coming to Chiang Mai during the burning season, in fact I'd recommend leaving it for South Thailand or neighbouring countries.&lt;br/&gt;
That being said some few people are absolute fine with it so personal tolerance definitely plays a role here.  &lt;/p&gt;
&lt;h1 id="leisure-time-and-conclusions"&gt;Leisure Time and Conclusions&lt;/h1&gt;
&lt;p&gt;There are a lot of things to do in Chiang Mai, most of it involves the beautiful nature of North Thailand but there's good amount of entertainment too. Chiang Mai has many shopping malls that provides relatively cheap and enjoyable shopping experience, there are some activities and events happening every week. There's a healthy music scene and a sizable expat community for various activities ranging from board-game evenings to book and yoga clubs.  &lt;/p&gt;
&lt;p&gt;&lt;a href="/images/cm-lake.jpg"&gt;&lt;img class="bigc" loading="lazy" src="/images/cm-lake.jpg" title="cosy little lake almost at the city center"/&gt;&lt;/a&gt;&lt;figcaption&gt;cosy little lake almost at the city center&lt;/figcaption&gt;&lt;/p&gt;
&lt;p&gt;To summarise Chiang Mai is a really fun, affordable and cosy town. The housing is amazing so is the food and nature. It's a great place to live in for 8/12 months and you really have to hermit your way through the 4 months of burning season (or ideally leave) but this can be planned around.&lt;/p&gt;
&lt;p&gt;The year I spent here was really enjoyable with the exception of burning season. I really enjoy North Thailand nature and there's always some new place to explore. I got driver license, scooter and a car which I believe are vital to truly enjoy the exploration and nature aspect of the area (maybe you could do without a car but with big bike instead).  &lt;/p&gt;
&lt;p&gt;I plan to stay here longer so there are many more blogpost to come! I plan to cover some subjects like expenses, food and my favorite activities in greater detail. &lt;/p&gt;
&lt;p&gt;For more Chiang My photos check out my &lt;a href="https://pixelfed.social/Wraptile"&gt;pixelfed feed&lt;/a&gt;.&lt;/p&gt;
&lt;h1 id="credits-and-attributions"&gt;Credits and attributions&lt;/h1&gt;
&lt;p&gt;1 - https://commons.wikimedia.org/wiki/User:Takeaway&lt;/p&gt;</content><category term="self"></category><category term="travel"></category><category term="thailand"></category><category term="chiang-mai"></category></entry><entry><title>Diving Into Web-Crawling</title><link href="https://granitosaur.us/starting-to-crawl.html" rel="alternate"></link><published>2018-12-10T00:00:00+01:00</published><updated>2018-12-10T00:00:00+01:00</updated><author><name>Bernardas Ališauskas</name></author><id>tag:granitosaur.us,2018-12-10:/starting-to-crawl.html</id><summary type="html">&lt;p&gt;Where to start with the art of hoarding online data?&lt;/p&gt;</summary><content type="html">&lt;p&gt;Web crawling is a brilliant source to bootstrap your application. Almost every application requires data of some sort and why not just pick up some public data available on the world wide web!&lt;/p&gt;
&lt;p&gt;In this introduction I'll cover the core ideas behind web-crawling and web-crawling with python.&lt;/p&gt;
&lt;h1 id="whats-web-crawling"&gt;What's Web Crawling?&lt;/h1&gt;
&lt;p&gt;First it's important to wrap your head around the stages of web crawling program.  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;crawler&lt;/strong&gt; or &lt;strong&gt;spider&lt;/strong&gt;
A program that connects to web pages and downloads their contents. &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To put it simply it's a program that goes online and finds two things:  &lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;data the user is looking for.  &lt;/li&gt;
&lt;li&gt;more targets to crawl &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;e.g.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Go to http://shop.com&lt;/li&gt;
&lt;li&gt;Find product pages&lt;/li&gt;
&lt;li&gt;Find and download product data such as price, title, description&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id="stages"&gt;Stages&lt;/h1&gt;
&lt;p&gt;&lt;img alt="basic crawl loop" src="https://granitosaur.us/images/crawl-loop.png"/&gt;&lt;/p&gt;
&lt;p&gt;A usual web-crawler program will be made from 4 core stages:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Discovery - find product urls to crawl&lt;/li&gt;
&lt;li&gt;Consumer - consume product urls and retrieve their htmls&lt;/li&gt;
&lt;li&gt;Parser - parse html data into something useful&lt;/li&gt;
&lt;li&gt;Processor - process the data with pipeline, filters etc.&lt;/li&gt;
&lt;li&gt;Loop!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;note: these stages don't have to have to happen in strict order&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;There can be few more steps in between but the core logic of a web crawler looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img alt="basic crawler" src="https://granitosaur.us/images/crawling.png"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;worth noting that websites can return all sorts of content not only html. Some return json, some just text and some code like javascript.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In very simplest python pseudo code for a crawler would look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'https://www.python.org/jobs/'&lt;/span&gt;

&lt;span class="n"&gt;job_urls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;find_job_urls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;consume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job_urls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'output.json'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can find full crawler at the end of blog post.&lt;/p&gt;
&lt;h1 id="crawling"&gt;Crawling&lt;/h1&gt;
&lt;p&gt;There are two ways to crawl http urls: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Synchronous - slow and simple &lt;/li&gt;
&lt;li&gt;Asynchronous - complex but blazing fast.  &lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Which one to use?&lt;/strong&gt; As beginner or if you don't need speed stick with synchronous approach. The programming is much more simple and easier to debug. However asynchronous knowledge is very valuable and learning it is a great idea&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="synchronous"&gt;Synchronous&lt;/h2&gt;
&lt;p&gt;This is very straight-forward approach. Everything goes in your program's order: &lt;/p&gt;
&lt;p&gt;&lt;img alt="basic crawl loop" src="https://granitosaur.us/images/crawl-loop.png"/&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;for every url: download page -&amp;gt; parse it -&amp;gt; store it -&amp;gt; repeat&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For synchronous crawling most popular library is &lt;a href="http://docs.python-requests.org/en/master/"&gt;requests&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;However while this approach is simple and easy to maintain it ends up being very slow as every time program connects to a webpage to download data program need to wait for it to response - in the mean time program could be doing something else: like download &lt;em&gt;another&lt;/em&gt; webpage, parse the data or store it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;when is synchronous approach good enough?&lt;/strong&gt; Often a lot of applications don't need to retrieve a lot of data (e.g. football match score crawler) then sync code is more than enough. &lt;br/&gt;
Alternatively a lot of website have request limits that are high enough that match synchronous code slowness. &lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="asynchronous"&gt;Asynchronous&lt;/h2&gt;
&lt;p&gt;Async programming is a bit more complex to wrap your head around but to put it shortly it allows the code to be executed in pararel.  &lt;/p&gt;
&lt;p&gt;Your program can schedule 100 request to a website &lt;em&gt;at once&lt;/em&gt; and handle response as they come in or do something else!  &lt;/p&gt;
&lt;p&gt;&lt;img alt="async crawl loop" src="https://granitosaur.us/images/crawl-loop-async.png"/&gt;&lt;/p&gt;
&lt;p&gt;So it terms of speed it vastly outperforms synchronous crawlers as they don't have to wait!&lt;/p&gt;
&lt;p&gt;&lt;img alt="basic crawler" src="https://granitosaur.us/images/sync_v_async.png"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;"checking" square here is very simplified representation of async mechanism&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For asynchronous crawling there are a lot of choices and no clear defacto standards. I recommend &lt;a href="https://github.com/ross/requests-futures"&gt;requests-futures&lt;/a&gt; for caroutine based approach and &lt;a href="https://twistedmatrix.com/"&gt;twisted&lt;/a&gt; or &lt;a href="https://github.com/twisted/treq"&gt;treq&lt;/a&gt; for callback based approach.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;caroutines or callbacks?&lt;/strong&gt; While caroutines are much more favored async principle these days callbacks have a special place in web-crawling community as the logic tends to match scraping patterns better&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1 id="parsing_1"&gt;Parsing&lt;/h1&gt;
&lt;p&gt;There are all sorts of data types on the web, but most likely you're either will be crawling &lt;code&gt;html&lt;/code&gt; or &lt;code&gt;json&lt;/code&gt;. &lt;/p&gt;
&lt;h2 id="html"&gt;Html&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Html&lt;/code&gt; is a subset of xml tree structures. It's a great data type for representing structure, however it's not a great data type for representing data itself.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; rubber chicken &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;price&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; 55.99 &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So usually this data is either converted to database tables or json, csv documents:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;csv documents:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;name,price   
rubber chicken, 55.99
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;json documents:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;[
    {
        "name": "rubber chicken",
        "price": 55.99
    } 
]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;database tables&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;------------------------------
|       name       |  price  |
------------------------------
|  rubber chicken  |  55.99  |
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What data type to chose for output?&lt;/strong&gt; Anything works! However some data types are easier to work than others. &lt;code&gt;json&lt;/code&gt; is an easy to format to work with as it translates to python &lt;code&gt;dict&lt;/code&gt; seamlessly. &lt;code&gt;csv&lt;/code&gt; is great to work with as it's an easy format to write and parse. &lt;code&gt;json-lines&lt;/code&gt; format is best of both worlds. 
&lt;strong&gt;What about data bases?&lt;/strong&gt; Document based databases are often preferred when web-scraping. Like &lt;a href="https://www.mongodb.com/"&gt;MongoDB&lt;/a&gt; and &lt;a href="http://couchdb.apache.org/"&gt;couchDB&lt;/a&gt;, they are great for storing json data, while relation databases are a bit less straight-forward but come with their own benefits (like &lt;a href="https://sqlite.org/index.html"&gt;sqlite&lt;/a&gt; and &lt;a href="https://mariadb.org/"&gt;mariadb&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="parsing-html"&gt;Parsing html&lt;/h3&gt;
&lt;p&gt;Since html is a structural data type we can parse it quite easily. For that there are &lt;a href="https://www.w3schools.com/xml/xpath_intro.asp"&gt;xpath&lt;/a&gt; and &lt;a href="https://www.w3schools.com/csSref/css_selectors.asp"&gt;css&lt;/a&gt; selectors and appropriate python libraries that implement this selector logic.&lt;/p&gt;
&lt;p&gt;For python all you need is &lt;a href="https://github.com/scrapy/parsel"&gt;parsel&lt;/a&gt; which allows you to use both types of selectors to parse data. Alternatively you can also use a popular alternative &lt;a href="https://www.crummy.com/software/BeautifulSoup/bs4/doc/"&gt;beautifulsoup4&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Here's a &lt;code&gt;parsel&lt;/code&gt; example:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;parsel&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Selector&lt;/span&gt;

&lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"""&lt;/span&gt;
&lt;span class="s2"&gt;    &amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;        &amp;lt;body&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;            &amp;lt;div id=name&amp;gt; rubber chicken &amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;            &amp;lt;div id=price&amp;gt; 55.99 &amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;        &amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;    &amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;"""&lt;/span&gt;
&lt;span class="n"&gt;selector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Selector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# css selector&lt;/span&gt;
&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;selector&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'#name::text'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extract_first&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# xpath selector&lt;/span&gt;
&lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;selector&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xpath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'//div[@id="price"]/text()'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extract_first&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# ' 55.99 ',' rubber chicken '&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Surprisingly simple!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Which one, css or xpath?&lt;/strong&gt;
Generally css selectors are much easier and more bautiful but xpath selectors are much powerful. So ideally use css and fallback to xpath when encountering something more complicated.  &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Parsing with regex?&lt;/strong&gt;
Generally parsing html with regex is a bad idea as regex patterns will quickly become unreliable. Html is structure data type - embrace it!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="json_1"&gt;Json&lt;/h2&gt;
&lt;p&gt;This type of data is often used by website internally together with javascript. Sometimes you can crawl these pages directly either through public or internal websites API. &lt;/p&gt;
&lt;p&gt;It's super convenient as you don't need to do any parsing yourself! &lt;/p&gt;
&lt;h4 id="parsing-json"&gt;Parsing json&lt;/h4&gt;
&lt;p&gt;Parsing json is super easy as it can be read as python dictionary right out of the box:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;
&lt;span class="n"&gt;json_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'{"data": [{"name": "product", "price": 55.99}]}'&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'data'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'price'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="c1"&gt;# 55.99&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;These days more and more web pages become dependant on javascript thus often providing json data&lt;/em&gt;&lt;/p&gt;
&lt;h1 id="synchronous-example_2"&gt;Synchronous Example&lt;/h1&gt;
&lt;p&gt;Lets write a simple article crawler for python blog posts!&lt;/p&gt;
&lt;p&gt;for this we'll use &lt;a href="https://github.com/scrapy/parsel"&gt;parsel&lt;/a&gt; and &lt;a href="http://docs.python-requests.org/en/master/"&gt;requests&lt;/a&gt; packages. You can get them via pip:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip install parsel requests
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Our spider logic would follow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Go to page with all links to blog posts&lt;/li&gt;
&lt;li&gt;Go to every blog post&lt;/li&gt;
&lt;li&gt;Extract data&lt;/li&gt;
&lt;li&gt;Store data to file&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;urllib.parse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urljoin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unquote&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;parsel&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Selector&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# 1&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;discover&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sd"&gt;"""Discover job urls in jobs listing page"""&lt;/span&gt;
    &lt;span class="n"&gt;jobs_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'https://www.python.org/jobs/'&lt;/span&gt;

    &lt;span class="n"&gt;jobs_html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jobs_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
    &lt;span class="n"&gt;jobs_sel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Selector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;jobs_html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;urls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jobs_sel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'.listing-company-name a::attr(href)'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;urljoin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jobs_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;"""Parse job html"""&lt;/span&gt;
    &lt;span class="n"&gt;sel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Selector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;company&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'.listing-location a::text'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extract_first&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'.reference.external::attr(href)'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extract_first&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xpath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"//h2[contains(text(),'Job Title')]"&lt;/span&gt;
                      &lt;span class="s2"&gt;"/following-sibling::text()"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extract_first&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xpath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"//h2[contains(text(),'Job Title')]"&lt;/span&gt;
                            &lt;span class="s2"&gt;"/following-sibling::p/text()"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extract_first&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s1"&gt;'url'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'location'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'title'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="s1"&gt;'description'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;unquote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;':'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;"""Here usually you'd add extra processing steps&lt;/span&gt;
&lt;span class="sd"&gt;    like upload to database&lt;/span&gt;
&lt;span class="sd"&gt;    or adding crawl time&lt;/span&gt;
&lt;span class="sd"&gt;    or determening whether to drop results based on their values"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;consume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;"""Consume job urls by downloading them, parsing data and saving to disk"""&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;'crawling job: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;crawl&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sd"&gt;"""&lt;/span&gt;
&lt;span class="sd"&gt;    Complete crawl loop:&lt;/span&gt;
&lt;span class="sd"&gt;    1. Discover job listing urls&lt;/span&gt;
&lt;span class="sd"&gt;    2. parse html for data&lt;/span&gt;
&lt;span class="sd"&gt;    3. process data&lt;/span&gt;
&lt;span class="sd"&gt;    4. store data&lt;/span&gt;
&lt;span class="sd"&gt;    """&lt;/span&gt;
    &lt;span class="n"&gt;urls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;discover&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;consume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'collected.json'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'__main__'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;crawl&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As you can see the crawler is split into 4 parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Discovery - discover job urls in listing page&lt;/li&gt;
&lt;li&gt;Consumer - crawl job urls&lt;/li&gt;
&lt;li&gt;Parser - parse data from job htmls&lt;/li&gt;
&lt;li&gt;Processor - process data and save it to file&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;1&lt;/strong&gt; - connection sessions establish connection to server and keeps it open for more requests, this speeds up crawling and puts less stress on the host.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1 id="conclusion"&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Data is often the core of application functionality and web-crawling is a great tool to easily take advantage of public data available online.  &lt;/p&gt;
&lt;p&gt;Crawling is a diverse, multi-step process with a lot of viable approaches but to start off sticking with syncrhonious &lt;code&gt;requests&lt;/code&gt; and html parsers like &lt;code&gt;parsel&lt;/code&gt; can be more than enough for most projects.&lt;/p&gt;
&lt;p&gt;For further reading it's important to take a look at &lt;strong&gt;web caching&lt;/strong&gt; and &lt;strong&gt;rate limiting&lt;/strong&gt;, &lt;strong&gt;proxies&lt;/strong&gt; and &lt;strong&gt;failure and memory managing&lt;/strong&gt;. Finally there's a whole other problem of &lt;strong&gt;scaling&lt;/strong&gt; both crawling and data storage when it comes to millions of results. &lt;br/&gt;
I'll be covering these in later blogs &lt;/p&gt;</content><category term="code"></category><category term="scrapy"></category><category term="python"></category><category term="web-crawling"></category></entry><entry><title>How to fix redis busy</title><link href="https://granitosaur.us/redis-busy.html" rel="alternate"></link><published>2018-08-28T00:00:00+02:00</published><updated>2018-08-28T00:00:00+02:00</updated><author><name>Bernardas Ališauskas</name></author><id>tag:granitosaur.us,2018-08-28:/redis-busy.html</id><summary type="html">&lt;p&gt;How to fix annoying redis busy error when working with redis lua scripts and multiple clients&lt;/p&gt;</summary><content type="html">&lt;p&gt;Imagine scenario where you have a single redis server (which is single threaded) and you have multiple clients:&lt;/p&gt;
&lt;p&gt;&lt;img alt="redis structure" src="https://granitosaur.us/images/redis-busy1.png"/&gt;&lt;/p&gt;
&lt;p&gt;Eventhough redis is single-threaded it has no problem handling this setup since it's blazing fast and knows how to manage it's task queue.&lt;br/&gt;
However if we ask redis server to perform a lua script evaluation it might take a while, which ends up with clients getting this nasty error:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;redis.exceptions.ResponseError: BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="redis structure" src="https://granitosaur.us/images/redis-busy2.png"/&gt;&lt;/p&gt;
&lt;h1 id="the-fix"&gt;The Fix&lt;/h1&gt;
&lt;p&gt;Thi fix this we need to apply a fix to the client side - make it wait and retry this &lt;code&gt;ResponseError&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;To do this lets patch &lt;code&gt;Redis&lt;/code&gt; class in python's &lt;code&gt;redis-py&lt;/code&gt; client:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;redis&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Redis&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyRedis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;lua_retry_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;  &lt;span class="c1"&gt;# how many times to retry&lt;/span&gt;

    &lt;span class="c1"&gt;# override execute to retry busy errors &lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;execute_command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lua_retry_time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute_command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;wait_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;wait_time&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lua_retry_time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute_command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;ResponseError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s1"&gt;'busy redis is busy'&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;wait_time&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# only print once&lt;/span&gt;
                    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Redis is busy, waiting up to 120 seconds...'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;wait_time&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute_command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Since every command in this client goes through &lt;code&gt;execute_command&lt;/code&gt; method we can override it to keep retrying &lt;code&gt;Redis BUSY&lt;/code&gt; errors for a fixed amount of time.&lt;/p&gt;
&lt;p&gt;With this patch Redis client will retry the connection every 2 seconds if redis is busy evaluating LUA.&lt;/p&gt;
&lt;p&gt;&lt;img alt="redis final structure" src="https://granitosaur.us/images/redis-busy3.png"/&gt;&lt;/p&gt;
&lt;h1 id="conclusion"&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;I've been trying to find some in-built solution for this and couldn't believe there's no official approach, neither with redis itself nor python's redis client. &lt;br/&gt;
The internet resources are very scarce around this issue too, so I figured I'd try and remedy this.&lt;/p&gt;</content><category term="code"></category><category term="linux"></category><category term="guide"></category><category term="redis"></category><category term="python"></category></entry><entry><title>Quest Accepted: Travel to HCMC, Vietnam</title><link href="https://granitosaur.us/quest-hcmc.html" rel="alternate"></link><published>2018-01-18T00:00:00+01:00</published><updated>2018-01-18T00:00:00+01:00</updated><author><name>Bernardas Ališauskas</name></author><id>tag:granitosaur.us,2018-01-18:/quest-hcmc.html</id><summary type="html">&lt;p&gt;My and my girlfriend's struggle to get to Ho Chi Minh City, Vietnam&lt;/p&gt;</summary><content type="html">&lt;p&gt;Me and my girlfriend are spending 3 months in Vietnam this year (amount tourist visa allows you) and our first stop is the biggest city in Vietnam, and the biggest city we've been to in general!  &lt;/p&gt;
&lt;h1 id="quest-1-getting-there"&gt;Quest #1: Getting There&lt;/h1&gt;
&lt;p&gt;Our original plan was to go fly out from Tartu to Helsinki, chill there for 8 hours (literally) then take an 8 hour flight to Bangkok, chill there for two hours (literally too, air conditioning is strong in asia) and finally take a two hour flight to HCMC.  &lt;/p&gt;
&lt;iframe allow="autoplay; encrypted-media" allowfullscreen="" frameborder="0" height="315" src="https://www.youtube.com/embed/MeJzHSxRq40" width="560"&gt;&lt;/iframe&gt;
&lt;p&gt;It didn't go exactly as planned.  &lt;/p&gt;
&lt;p&gt;Before we left to Helsinki we did a final checkup and turns out our flight carrier Jetstar does not offer online check-in for our trip - this means we would have to leave the international area of the airport, check in, get back to the international area, and get to our gate for the flight. 
To leave the international area you need to get a visa and we only had 2 hours there so there would have been almost no way for us to make it... Well so we thought. Even Jetstar support "specialist" assured us that is the case:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"you need to get out to check in, would you like to reschedule for an extra fee?". &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Nevertheless we found it a bit hard to believe and went to reddit, asked some people who had done these kinds of trips before on &lt;a href="http://reddit.com/r/digitalnomads"&gt;/r/digitalnomads&lt;/a&gt; and turns out big airports like the one in Bangkok have check-in booths &lt;strong&gt;in&lt;/strong&gt; the international areas so we didn't have worry about anything! Crisis Averted!   &lt;/p&gt;
&lt;video autoplay="" height="240" loop="" muted="" title="Close call though!" width="480"&gt;&lt;source src="https://granitosaur.us/gifs/spongebob_crisis.mp4" type="video/mp4"/&gt;&lt;/video&gt;
&lt;p&gt;Well we weren't too certain, but we were at least somewhat relieved. &lt;/p&gt;
&lt;p&gt;However our adventure was not over.&lt;br/&gt;
Once we got to &lt;strong&gt;Helsinki&lt;/strong&gt; we found out that everything &lt;strong&gt;is really bloody expensive&lt;/strong&gt;! We chilled in a mall near the airport for 8 hours trying to not go bankrupt and it was a &lt;em&gt;long&lt;/em&gt;, dull experience. Also I got a bit of a fever so that definitely didn't make it any more exciting.&lt;br/&gt;
Finally we decided to go to the airport early, which was a great idea as the &lt;strong&gt;buses in Finland don't stop unless you put your hand out&lt;/strong&gt; and wave at them like your life depends on it. &lt;br/&gt;
Needless to say we missed our bus because we were not aware of this great ritual of Finnish people and enjoyed 25 more minutes of lovely Finnish weather.&lt;br/&gt;
Waving hands at busses? Pretty barbaric if you ask me.  &lt;/p&gt;
&lt;video autoplay="" loop="" muted="" title="I guess this episode was written by someone who has been in Finaland" width="480"&gt;&lt;source src="https://granitosaur.us/gifs/spongebob_bus.mp4" type="video/mp4"/&gt;&lt;/video&gt;
&lt;p&gt;There's more!&lt;br/&gt;
Finally we got onto our plane from Helsinki to Bangkok. It was a nice plane and the flight went pretty smooth, except that we were late. The flight wasn't delayed or anything but we were almost an hour late for whatever reason.  &lt;/p&gt;
&lt;p&gt;We landed on time to still make it, still had an hour before check in closes, "We did it" so we've thought. Well our plane couldn't park for another 30 minutes as a plane in front of us had to be towed away as it's wheels were broken. So we are finally connected and we're on the very end of the plane and some folks being decided to chill in the corridor of the airplane with their toddlers and their bags. Politely we managed to climb through their barricades while avoiding the stink eyes and we made it out to the airport with 20 minutes to spare!  &lt;/p&gt;
&lt;p&gt;We had a 10 minute long sprint through the entire airport looking for Jetstar's booth, but it's nowhere to be found. Finally we found a gate with a small placard saying something related to our journey. There's was a lady standing there and she looked confused when we passed her our phones with flight tickets on them. She asks some questions that we couldn't really make out, we nodded and pointed and weirdly enough we got let through!  &lt;/p&gt;
&lt;video autoplay="" loop="" muted="" title="We really had to leg it, we even ran on the moving walkways. We really went FAST!" width="480"&gt;&lt;source src="https://granitosaur.us/gifs/sanic.mp4" type="video/mp4"/&gt;&lt;/video&gt;
&lt;p&gt;We had our bags checked and had 10 minutes to spare! We went on looking for jetstar's booth again but the info desk just pointed us to the gate from which our plane was about to leave. Well the people at the gate told us that we messed up, "how did you even get here without a boarding pass and without going through immigration?" they asked. We explained our story briefly and luckily for us they took pity on us and we were issued boarding passes right there at the gate, and had 5 minutes left to boot!&lt;br/&gt;
Plan was very calculated to say the least!&lt;/p&gt;
&lt;p&gt;The last step of our trip was complete! We finally felt relieved, and this final flight between Bangkok and Ho Chi Minh City was the most enjoyable flight of my life - it felt as we're flying first class!&lt;/p&gt;
&lt;h1 id="quest-2-get-to-the-apartment"&gt;Quest #2: Get to the apartment&lt;/h1&gt;
&lt;p&gt;We actually landed early, not that it mattered this time. The airport was pleasant and straight-forward. We followed "visa" arrows and finally arrived at the visa issue area, passed our documents and passports to a very bored looking policeman and sat down to wait for our turn.&lt;br/&gt;
Every few minutes you could hear a lady mumble something over the speaker and a bunch of foreigners starting to look confused, trying to decipher whether it was their name mentioned there and whether they should try and shuffle to the window to receive their passports back.&lt;br/&gt;
Finally after 20-or-so minutes our time came and we were called to take our documents and pay up. &lt;/p&gt;
&lt;p&gt;All things considered this was one of the most pleasant visa experiences I've had, almost as if it was there just to collect a 25 dollar fee &amp;macr;\_(ツ)_/&amp;macr;&lt;/p&gt;
&lt;p&gt;We almost made it. &lt;br/&gt;
Two more steps to go: &lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Exchange some money  &lt;/li&gt;
&lt;li&gt;get to our airbnb place(which was pretty far away.)  &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once we left the gated airport area we were greeted by shouts and offers: "Taxi, Taxi!", "Sim SIM", "Exchange exchange!". We kinda needed all of them but were intimidated by the aggressiveness of the sellers.  &lt;/p&gt;
&lt;video autoplay="" loop="" muted="" title="1 usd = 26000 dong, we did feel like we were becoming millionaires!" width="480"&gt;&lt;source src="https://granitosaur.us/gifs/spongebob_money.mp4" type="video/mp4"/&gt;&lt;/video&gt;
&lt;p&gt;We looked through the booths by standing far away to avoid the calls and decided on a few. They all offered pretty much the same service. For whatever reason we chose the loud lady at the first booth we saw as she wrote down the biggest number on a paper piece as what she would give us per 1 dollar. We handed her over $500 and she quickly turned us away with a stack of vietnamese dongs as she was about to receive a few more customers. &lt;br/&gt;
We were a bit confused and shocked, so we decided to whip out a calculator and see whether we got scammed. &lt;br/&gt;
After fiddling around we were surprised that we were $60 short! Instead of 12-something million we received only 10. Needless to say we went back to complain and the lady was pretending to be confused: &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"Oh you wanted this rate on the wall?" (as opposed to my imaginary rate).  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;She quickly went through her computer, printed us a receipt and handed us the money we were missing. This time we counted every single dong to make sure we got everything.  &lt;/p&gt;
&lt;p&gt;Next we got some sim cards with unlimited data for 150k a pop, it was a pretty good deal, especially compared to european prices. We planned on taking an uber but we got a bit lost on the map and our phone batteries were almost dead. Instead we wrote the address of our place down on a piece of paper before the last electrons evaporated from the lithium batteries and went to a pre-paid taxy booth. It's a booth with some people yelling "Taxi, Taxi" every 30 seconds or so where you pay before you go to prevent being scammed.&lt;br/&gt;
The guys at the booth looked at the address paper confused and said they'll get us there for 200k. We didn't have much choice and 7 euros didn't sound that much for a couple of lost and tired Europeans.  &lt;/p&gt;
&lt;p&gt;We got the paper went outside and were greeted by a guy who took us to our cab. So we started the last bit of our trip. The streets were just packed and guzzling with motorbikes and here we were sitting in a clearly oversized for this region SUV and dragging along through the tiny streets of Saigon.  &lt;/p&gt;
&lt;p&gt;&lt;img src="https://granitosaur.us/gifs/dogo_taxi.gif" title="When everyone is driving a scooter you look a bit of an idiot sitting in an oversized car"/&gt;&lt;/p&gt;
&lt;p&gt;We didn't mind taking our time though. The car was comfy and the views outside were quite mind-blowing! There was a scooter with two people and a dog just standing on the back of it. There was another one with 4 people stacked on it - two parents and two kids. And another one with chickens on it! And the way the whole traffic worked, or didn't. And the way the sidewalks were covered with fruits, produce and even raw meat, occasionally full dead bodies of chicken, ducks and other animals!&lt;br/&gt;
I think we were too tired and jetlagged to have a culture shock, but we definitely felt alien.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://granitosaur.us/images/hcmc_4people_bike.jpg" title="I'm surprised the cheap chinese scooters still manage to survive there"/&gt;&lt;/p&gt;
&lt;p&gt;Our driver finally stops, whips out google translate and says that our hotel is somewhere here but it's closed. We correct him that it's not a hotel but he still says it should be somewhere here. We looked around and the place looked familiar and some of the houses even looked like the ones in the pictures our airbnb host sent us - we had the address, surely we can find it.&lt;br/&gt;
As we were about to leave the vehicle our driver used google translate to ask for a $1 tip "for a drink or food". Being tired and just wanting to get away we decided to humor him and give him that dollar.  &lt;/p&gt;
&lt;p&gt;He wished us goodbye and we set off. We stumbled around looking for our apartment and finally decided to boot up our laptop and charge our phone off it to get maps going. Turns out &lt;strong&gt;we weren't at our destination at all!&lt;/strong&gt;&lt;br/&gt;
 Google map directions made out an over 500 meter long zig-zag route that we had to take. That doesn't sound like a lot we thought and went forth. Well it was quite a bit, we went through tiny streets filled with scooters and street dogs that followed us around. 20 minutes later we finally arrived there, at least according to the google maps app. However the place was nowhere to be seen. We tried to call our host but our sims were not capable of calls - only online mobile data. Finally we managed to somehow get in touch with our host and we described where we are and he offered to come pick us up in 15 minutes. &lt;/p&gt;
&lt;p&gt;While waiting and walking around we actually managed to find the place! It was a tall white house with a huge gate on a small side-street nearby. We did it, we got greeted by an old lady that let us in to our apartment and we could lay down on the bed and say for sure that we "made it!".&lt;/p&gt;
&lt;h1 id="lessons-learned"&gt;Lessons Learned&lt;/h1&gt;
&lt;video autoplay="" loop="" muted="" title="a bunch of obvious conclusions" width="480"&gt;&lt;source src="https://granitosaur.us/gifs/spongebob_book.mp4" type="video/mp4"/&gt;&lt;/video&gt;
&lt;p&gt;In general it wasn't as bad as it could have been. I'd say we got lucky at multiple points through out our trip and somewhat unlucky at others. Nevertheless we made it without any losses or injuries and in hindsight it wasn't bad or difficult at all, just &lt;em&gt;inconvenient&lt;/em&gt;.  &lt;/p&gt;
&lt;p&gt;Tips for future traveling:  &lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Research airport structures. Make sure to get online checkin. Check in as early as possible.  &lt;/li&gt;
&lt;li&gt;Do not trust money exchanges.&lt;/li&gt;
&lt;li&gt;Carry multiple mini-usb cables/power banks for phone charging.  &lt;/li&gt;
&lt;li&gt;Do not trust taxi services too much.  &lt;/li&gt;
&lt;li&gt;Finland is weird and expensive.  &lt;/li&gt;
&lt;/ol&gt;</content><category term="self"></category><category term="travel"></category><category term="vietnam"></category></entry><entry><title>Why I use GPL license</title><link href="https://granitosaur.us/why-gpl.html" rel="alternate"></link><published>2017-11-18T00:00:00+01:00</published><updated>2017-11-18T00:00:00+01:00</updated><author><name>Bernardas Ališauskas</name></author><id>tag:granitosaur.us,2017-11-18:/why-gpl.html</id><summary type="html">&lt;p&gt;Why I use GNU's General Public License and why I think it's a superior free or even open software license and anyone should consider it for your project.&lt;/p&gt;</summary><content type="html">&lt;p&gt;This issue has been discussed to death and I still find people blindly advocating MIT or even WTFPL licenses.
I'm far from a lawyer but I feel that there are few very generic and simple details about software licensing people
who lead open software projects should understand.&lt;/p&gt;
&lt;h2 id="protect-yourself"&gt;Protect Yourself&lt;/h2&gt;
&lt;p&gt;First thing first - the license should protect the project contributors and creators. In legal terms it's called no-warranty-provided and exlucsion of it can be dangerous.&lt;br/&gt;
Technology law sucks -
it's outdated, overly complicated and widely regional where software itself has no borders.&lt;/p&gt;
&lt;p&gt;To illustrate tis danger imagine John made a library and used a license that does not fully protect the creators
from warranty responsibilities(like WTFPL). Now some company decides to use John's work in their software stack and
unfortunately John left a bug in his library that either exposed the company to theft or caused their technology to break. In this case the company could easily start lawsuit against John and argue that it was John's open source library that cause them to go under and John would be liable.&lt;/p&gt;
&lt;p&gt;Most linceses however have this included, GPL for example:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software.  For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;While some occasional ones like WTFPL don't.&lt;/p&gt;
&lt;p&gt;To summarise, whatever license you use make sure to protect yourself - make sure you provide no warranty!&lt;/p&gt;
&lt;h2 id="philosophy-and-copyleft"&gt;Philosophy and Copyleft&lt;/h2&gt;
&lt;p&gt;It's important to consider license philosophy as well.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Copyleft is a general method for making a program (or other work) free
(in the sense of freedom, not &amp;ldquo;zero price&amp;rdquo;), and requiring all modified
and extended versions of the program to be free as well.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For example GPL is a copyleft license. It's a "viral" license, 
which means any forks or modifications (that are published) have to also
carry the same license. This protects the projects ecosystem and enables 
a bunch of benefits for everyone involved.&lt;/p&gt;
&lt;h3 id="benefits"&gt;Benefits&lt;/h3&gt;
&lt;p&gt;On the other hand it's also worth considering the &lt;strong&gt;benefits&lt;/strong&gt; of viral copyleft nature of GPL type licenses:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Patches - since the license is viral any changes or forks will be public and those changes can be applied
to the original project very easily. This huge reason why Linux kernel itself is licensed under GPL.&lt;/li&gt;
&lt;li&gt;Freedom - it continues the message of free software. As project lead you have the
power to propagate the message as any derivatives will have to carry your choice.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="restrictions"&gt;Restrictions&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;it's too restrictive and puts too many gates in education!  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This quote comes up a lot when people argue against GPL, however this argument is false as &lt;strong&gt;GPL's restriction only applies to published software&lt;/strong&gt;&lt;br/&gt;
(&lt;a href="https://www.gnu.org/licenses/gpl-faq.html#GPLRequireSourcePostedPublic"&gt;as per FAQ&lt;/a&gt;).
So in other words you can modify and learn from software all you want and use it (even for profit)
as long as you don't publish it and try to turn it into a product of your own.&lt;/p&gt;
&lt;p&gt;Is it too restrictive for business? Maybe, but why would you be concerned about business?
The ecosystem should not expect anything from a business, it puts an unnecessary
centralization to an unreliable source. At the end of the day a business is out there with
one goal in mind - to make money - it's not a bad goal, but putting any expectations
for a business to go out of their way and support the ecosystem is wildly unreasonable.&lt;/p&gt;
&lt;h2 id="closing-statement_1"&gt;Closing Statement&lt;/h2&gt;
&lt;p&gt;I'll continue using GPL license for all of my project where possible. It aligns with my ideology of software and
I believe I can develop and maintain software where I don't need to rely on adoption.&lt;/p&gt;
&lt;p&gt;Additional read:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.gnu.org/philosophy/pragmatic.html"&gt;Copyleft: Pragmactic Idealism&lt;/a&gt;&lt;br/&gt;
&lt;a href="https://www.gnu.org/licenses/gpl-faq.html"&gt;GPL's FAQ&lt;/a&gt;&lt;br/&gt;
&lt;a href="https://www.fsf.org/blogs/rms/selling-exceptions"&gt;It's possible to sell GPL exception&lt;/a&gt; &lt;/p&gt;</content><category term="code"></category><category term="floss"></category><category term="legal"></category></entry><entry><title>How to disable thinkpad trackpoint acceleration</title><link href="https://granitosaur.us/disable-thinkpad-acceleration.html" rel="alternate"></link><published>2017-11-17T00:00:00+01:00</published><updated>2017-11-17T00:00:00+01:00</updated><author><name>Bernardas Ališauskas</name></author><id>tag:granitosaur.us,2017-11-17:/disable-thinkpad-acceleration.html</id><summary type="html">&lt;p&gt;A short note on how to properly disable thinkpoint trackpad acceleration&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;I've got a new thinkpad laptop and I've been struggling with mouse acceleration on trackpoint,
turns out disabling it isn't as straight-forward as you'd expect&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;To get rid of this abomination of a feature that is enabled by default we need &lt;code&gt;xinput&lt;/code&gt;&lt;/p&gt;
&lt;h1 id="the-fix"&gt;The Fix&lt;/h1&gt;
&lt;p&gt;First you need to find the code or name of your trackpoint:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ xinput list
⎡ Virtual core pointer                          &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;    &lt;span class="o"&gt;[&lt;/span&gt;master pointer  &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="o"&gt;)]&lt;/span&gt;
⎜   &amp;rdsh; Virtual core XTEST pointer                &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;    &lt;span class="o"&gt;[&lt;/span&gt;slave  pointer  &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;)]&lt;/span&gt;
⎜   &amp;rdsh; SynPS/2 Synaptics TouchPad                &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;   &lt;span class="o"&gt;[&lt;/span&gt;slave  pointer  &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;)]&lt;/span&gt;
⎜   &amp;rdsh; TPPS/2 IBM TrackPoint                     &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;13&lt;/span&gt;   &lt;span class="o"&gt;[&lt;/span&gt;slave  pointer  &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;)]&lt;/span&gt;
⎣ Virtual core keyboard                         &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;    &lt;span class="o"&gt;[&lt;/span&gt;master keyboard &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;)]&lt;/span&gt;
    &amp;rdsh; Virtual core XTEST keyboard               &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;    &lt;span class="o"&gt;[&lt;/span&gt;slave  keyboard &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="o"&gt;)]&lt;/span&gt;
    &amp;rdsh; Power Button                              &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;    &lt;span class="o"&gt;[&lt;/span&gt;slave  keyboard &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="o"&gt;)]&lt;/span&gt;
    &amp;rdsh; Video Bus                                 &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;    &lt;span class="o"&gt;[&lt;/span&gt;slave  keyboard &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="o"&gt;)]&lt;/span&gt;
    &amp;rdsh; Sleep Button                              &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;    &lt;span class="o"&gt;[&lt;/span&gt;slave  keyboard &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="o"&gt;)]&lt;/span&gt;
    &amp;rdsh; Integrated Camera: Integrated C           &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;   &lt;span class="o"&gt;[&lt;/span&gt;slave  keyboard &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="o"&gt;)]&lt;/span&gt;
    &amp;rdsh; AT Translated Set &lt;span class="m"&gt;2&lt;/span&gt; keyboard              &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;11&lt;/span&gt;   &lt;span class="o"&gt;[&lt;/span&gt;slave  keyboard &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="o"&gt;)]&lt;/span&gt;
    &amp;rdsh; ThinkPad Extra Buttons                    &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;   &lt;span class="o"&gt;[&lt;/span&gt;slave  keyboard &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="o"&gt;)]&lt;/span&gt;
    &amp;rdsh; SteelSeries  SteelSeries Arctis &lt;span class="m"&gt;7&lt;/span&gt;         &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;    &lt;span class="o"&gt;[&lt;/span&gt;slave  keyboard &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="o"&gt;)]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Our guy is &lt;code&gt;TPPS/2 IBM Trackpoint&lt;/code&gt; aka id &lt;code&gt;13&lt;/code&gt;.
Next we can see the properties of this prop:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ xinput list-props &lt;span class="m"&gt;13&lt;/span&gt;
Device &lt;span class="s1"&gt;'TPPS/2 IBM TrackPoint'&lt;/span&gt;:
        Device Enabled &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;140&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:   &lt;span class="m"&gt;1&lt;/span&gt;
        Coordinate Transformation Matrix &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;142&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;: &lt;span class="m"&gt;1&lt;/span&gt;.000000, &lt;span class="m"&gt;0&lt;/span&gt;.000000, &lt;span class="m"&gt;0&lt;/span&gt;.000000, &lt;span class="m"&gt;0&lt;/span&gt;.000000, &lt;span class="m"&gt;1&lt;/span&gt;.000000, &lt;span class="m"&gt;0&lt;/span&gt;.000000, &lt;span class="m"&gt;0&lt;/span&gt;.000000, &lt;span class="m"&gt;0&lt;/span&gt;.000000, &lt;span class="m"&gt;1&lt;/span&gt;.000000
        libinput Natural Scrolling Enabled &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;310&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:       &lt;span class="m"&gt;0&lt;/span&gt;
        libinput Natural Scrolling Enabled Default &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;311&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:       &lt;span class="m"&gt;0&lt;/span&gt;
        libinput Left Handed Enabled &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;312&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:     &lt;span class="m"&gt;0&lt;/span&gt;
        libinput Left Handed Enabled Default &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;313&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:     &lt;span class="m"&gt;0&lt;/span&gt;
        libinput Accel Speed &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;314&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:     &lt;span class="m"&gt;0&lt;/span&gt;
        libinput Accel Speed Default &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;315&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:     &lt;span class="m"&gt;0&lt;/span&gt;.000000
        libinput Accel Profiles Available &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;316&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:        &lt;span class="m"&gt;1&lt;/span&gt;, &lt;span class="m"&gt;1&lt;/span&gt;
        libinput Accel Profile Enabled &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;317&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:   &lt;span class="m"&gt;1&lt;/span&gt;, &lt;span class="m"&gt;0&lt;/span&gt;
        libinput Accel Profile Enabled Default &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;318&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:   &lt;span class="m"&gt;1&lt;/span&gt;, &lt;span class="m"&gt;0&lt;/span&gt;
        libinput Scroll Methods Available &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;319&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:        &lt;span class="m"&gt;0&lt;/span&gt;, &lt;span class="m"&gt;0&lt;/span&gt;, &lt;span class="m"&gt;1&lt;/span&gt;
        libinput Scroll Method Enabled &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;320&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:   &lt;span class="m"&gt;0&lt;/span&gt;, &lt;span class="m"&gt;0&lt;/span&gt;, &lt;span class="m"&gt;1&lt;/span&gt;
        libinput Scroll Method Enabled Default &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;321&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:   &lt;span class="m"&gt;0&lt;/span&gt;, &lt;span class="m"&gt;0&lt;/span&gt;, &lt;span class="m"&gt;1&lt;/span&gt;
        libinput Button Scrolling Button &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;322&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;: &lt;span class="m"&gt;2&lt;/span&gt;
        libinput Button Scrolling Button Default &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;323&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;: &lt;span class="m"&gt;2&lt;/span&gt;
        libinput Middle Emulation Enabled &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;324&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:        &lt;span class="m"&gt;0&lt;/span&gt;
        libinput Middle Emulation Enabled Default &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;325&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:        &lt;span class="m"&gt;0&lt;/span&gt;
        libinput Send Events Modes Available &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;260&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:     &lt;span class="m"&gt;1&lt;/span&gt;, &lt;span class="m"&gt;0&lt;/span&gt;
        libinput Send Events Mode Enabled &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;261&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:        &lt;span class="m"&gt;0&lt;/span&gt;, &lt;span class="m"&gt;0&lt;/span&gt;
        libinput Send Events Mode Enabled Default &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;262&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:        &lt;span class="m"&gt;0&lt;/span&gt;, &lt;span class="m"&gt;0&lt;/span&gt;
        Device Node &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;263&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:      &lt;span class="s2"&gt;"/dev/input/event18"&lt;/span&gt;
        Device Product ID &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;264&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:        &lt;span class="m"&gt;2&lt;/span&gt;, &lt;span class="m"&gt;10&lt;/span&gt;
        libinput Drag Lock Buttons &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;326&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:       &amp;lt;no items&amp;gt;
        libinput Horizontal Scroll Enabled &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;327&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:       &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To disable acceleration we are interested in one of these two settings:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;        libinput Accel Speed (314):     0
        libinput Accel Profile Enabled (317):   1, 0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We can either diminish/change the acceleration speed by:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;# to disable we can use negative acceleration
$ xinput set-prop 13 314 -1
# or we can disable acceleration profile entirely
$ xinput set-prop 13 317 0 1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Note, don't use both, -1 with disabled acceleration pretty much disables the devices.&lt;/p&gt;
&lt;h1 id="make-it-persisntant"&gt;Make It Persisntant&lt;/h1&gt;
&lt;p&gt;To make the change persist after restart simply add the config line it to your &lt;code&gt;~/.xsessionrc&lt;/code&gt; file, e.g.:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;echo "xinput set-prop 13 317 0 1" &amp;gt;&amp;gt; ~/.xsessionrc
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="code"></category><category term="linux"></category><category term="guide"></category></entry><entry><title>Going to Vietnam!</title><link href="https://granitosaur.us/vietnam.html" rel="alternate"></link><published>2017-11-14T00:00:00+01:00</published><updated>2017-11-14T00:00:00+01:00</updated><author><name>Bernardas Ališauskas</name></author><id>tag:granitosaur.us,2017-11-14:/vietnam.html</id><summary type="html">&lt;p&gt;Preparing to spend 3 months in Vietnam, working remotely and experiencing a distance culture - an exciting proposition!&lt;/p&gt;</summary><content type="html">&lt;p&gt;Me and my girlfriend have been planning to spend this year somewhere abroad.
My first choice was Lisbon, Portugal as we visited Porto last year and
I've always wanted to spend a year or two living and experiencing Portugal.
However due to huge house market inflation (what was 500&amp;euro;/month two years ago
turned out to be almost 1000&amp;euro; now) it seemed to be a bit pricey.&lt;/p&gt;
&lt;p&gt;As an alternative we've started to look into South-East Asia, Vietnam and Thailand in particular.
Both of these countries are famous for expats and remote workers. After a bit of
research we've decided to go to Vietnam first as visa is easier to acquire and it's
 both of our first time dealing with visa-required traveling.&lt;/p&gt;
&lt;h1 id="vietnam"&gt;Vietnam&lt;/h1&gt;
&lt;p&gt;&lt;img alt="Vietnam size compared to EU" src="https://granitosaur.us/images/vietnam_scale.png"/&gt;&lt;/p&gt;
&lt;p&gt;Choosing a particular place to stay in Vietnam is a challenge on it's own since it's
a long noodle-like country that has surprisingly different environments - from busy,
motor-bike filled streets to white sand islands.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Ho Chi Minh City" src="https://granitosaur.us/images/hcmc.jpg"/&gt;&lt;/p&gt;
&lt;p&gt;Ho Chi Minh City (aka Saigon) is the biggest city in there so we decided to stay there for our
first month. It has a large expat community as well as being action packed and busy -
the complete opposite of a small Estonian town we're living in right now.&lt;/p&gt;
&lt;p&gt;You can get a nice place in the range of 300-500&amp;euro;/month. There are a few places to look
for great rent deals:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Airbnb.com - A lot of the places on airbnb offer monthly or weekly discounts. So despite
airbnb usually having inflated prices compared to the rest of the market it can still contain
a lot of good deals. Also it provides security and allows short term rent.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;expat.com - has &lt;a href="http://www.expat.com/en/housing/asia/vietnam/ho-chi-minh-city/"&gt;a lot of listings&lt;/a&gt;.
The only issue is that browsing is really tedious and difficult since there are no search filters.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On airbnb we managed to snag a 350&amp;euro; apartment that is close to District 1 which is the city center:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Ho Chi Minh City apartment" src="https://granitosaur.us/images/hcmc_apt.jpg"/&gt;&lt;/p&gt;
&lt;h2 id="work"&gt;Work&lt;/h2&gt;
&lt;p&gt;As there are a lot of remote workers from all around the world in HCMC, there are quite a few
coworking places to choose from.
I found &lt;a href="https://www.coworker.com/vietnam/ho-chi-minh-city"&gt;coworker.com&lt;/a&gt; and it seems that the monthly
prices are in the range of 50-100&amp;euro;. Most of the places offer reasonable scaling for shorter durations,
i.e. 1 week is 1/4 of monthly price.&lt;/p&gt;
&lt;p&gt;I'm not sure whether I'll go to any of those as I usually just prefer a cafe or a lounge of some sorts,
but nevertheless it could be a good opportunity to meet other expats!&lt;/p&gt;
&lt;h2 id="visa"&gt;Visa&lt;/h2&gt;
&lt;p&gt;Visa acquisition was as straight-forward as it gets!&lt;/p&gt;
&lt;p&gt;Just go to &lt;a href="https://www.visatovietnam.org/"&gt;visatovietnam.org&lt;/a&gt;, fill some basic data from your passport,
uncheck unnecessary "fast-forward" fees and pay up 19$ per person (with additional 25$ stamping fee on arrival)
for a 3 month-long single-entry visa. Two days later you'll get a confirmation letter - print out some simple papers,
get a photo of your mug and a scan of your passport and you're ready to go!&lt;/p&gt;
&lt;h2 id="flight"&gt;Flight&lt;/h2&gt;
&lt;p&gt;&lt;img alt="flight from Tartu to HCMC" src="https://granitosaur.us/images/tartu_hcmc_flight.png"/&gt;&lt;/p&gt;
&lt;p&gt;The flight prices are very reasonable. The month on November this year turned out to be especially cheap.
Our flight is &lt;code&gt;Tartu-Helsinki-Bangkok&lt;/code&gt; -&amp;gt; &lt;code&gt;Bangkok-HCMC&lt;/code&gt;
It's a bit tricky since not many flight searches offer direct flights to HCMC but you can simply organize it
yourself, especially if you do not have any luggage since you don't need to organize luggage transfer.
We bought tickets directly from Finnair and Jetstar. It's a tiny bit more expensive than buying through
various middleman websites but it certainly is safer and easier just to go through the official sources.&lt;/p&gt;
&lt;p&gt;For a one-way trip we ended up shelling out:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Tartu-Helsinki-Bangkok&lt;/code&gt; - 284&amp;euro;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Bangkok-HCMC&lt;/code&gt; - 66&amp;euro;&lt;/p&gt;
&lt;p&gt;This is with seat selection near the window.
Grand total of &lt;strong&gt;350&amp;euro;&lt;/strong&gt; is a pretty great deal for traveling half-way to the other side of the world!
And round trip is even cheaper if you're better at planning than I am.&lt;/p&gt;
&lt;h2 id="preparation"&gt;Preparation&lt;/h2&gt;
&lt;p&gt;&lt;img alt="Electrical socket" src="https://granitosaur.us/images/vietnam_socket.jpg"/&gt;&lt;/p&gt;
&lt;p&gt;Sockets - Vietnam uses different sockets than Europe. There are a few socket-types prevalent but by the looks
of it it's mostly Type A sockets. Vietnam also has different voltages of 110 to 220 V at 50Hz compared to
european 230V @ 50Hz. So in some rare occasions you'll want a power converter as well. However it's probably
easier to get the adapters and/or converters in Vietnam itself.&lt;/p&gt;
&lt;p&gt;We aren't really planning on taking anything other than laptops and a camera. There seems to be a decent
amount of snatch and pick pocket crimes but we have a secure backpack
(&lt;a href="https://www.xd-design.com/bobby"&gt;Bobby by XDDesign&lt;/a&gt;) to keep our valuables safe.
Vietnam is known for cheap products, especially when it comes to clothing, so really there's
hardly a need to take anything else, it's more fun to go shopping!&lt;/p&gt;
&lt;h2 id="follow-up"&gt;Follow Up&lt;/h2&gt;
&lt;p&gt;This is certainly exciting and I'll be sure to follow up this blog in the near future once we get there on Nov 22nd!&lt;/p&gt;
&lt;p&gt;I'll be sure to include spending reports and such as well.&lt;/p&gt;</content><category term="self"></category><category term="travel"></category><category term="vietnam"></category></entry><entry><title>Securing Data With eCryptfs</title><link href="https://granitosaur.us/ecrypt-yourself.html" rel="alternate"></link><published>2017-10-22T00:00:00+02:00</published><updated>2017-10-22T00:00:00+02:00</updated><author><name>Bernardas Ališauskas</name></author><id>tag:granitosaur.us,2017-10-22:/ecrypt-yourself.html</id><summary type="html">&lt;p&gt;protect your sensitive data like crypto wallets with eCrypt overlay filesystem!&lt;/p&gt;</summary><content type="html">&lt;p&gt;Since I started dabbling in Cryptocurrency I've been feeling a bit paranoid about protecting my wallet information and keys. Fortunately there are already tools that are dead easy to use. &lt;br/&gt;
The standard tool for linux is called eCryptfs and let me tell you how awesome and convenient it is!&lt;/p&gt;
&lt;h1 id="ecryptfs"&gt;eCryptfs&lt;/h1&gt;
&lt;p&gt;eCryptfs is a cryptographic overlay filesystem for Linux. It's a great tool to encrypt some private data like cryptocurrency wallet keys, private pictures &amp;mdash; anything really.&lt;br/&gt;
It allows you to mount a password protected, encrypted filesystem on your usual unencrypted filesystem:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;dex&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ls&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="n"&gt;Access&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Your&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Private&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;desktop&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;README&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;txt&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ecryptfs&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;mount&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Enter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;login&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;passphrase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Inserted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;xxxxxx&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;into&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;session&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;keyring&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;has&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;been&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mounted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;To&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;see&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;change&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;current&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;shell&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;dex&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;private&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="n"&gt;dex&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="n"&gt;dex&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ls&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="n"&gt;myetherwallet&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;btc&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Pretty damn cool - simple and secure!  &lt;/p&gt;
&lt;h2 id="setup"&gt;Setup&lt;/h2&gt;
&lt;p&gt;eCryptfs setup is dead easy!&lt;br/&gt;
Since linux version 3.18 eCrypt overlay filesystem is included with core kernel. 
Simply enable it with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;modprobe ecryptfs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then we need some tools to easily mount, unmount and generate our filesystem: &lt;code&gt;ecryptfs-utils&lt;/code&gt; has everything we would need and is available on every linux package manager:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo apt install ecryptfs-utils  # Ubuntu
sudo pacman -S ecryptfs-utils  # Arch
etc. etc.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Once you have it installed you'll find your user-space path populated with a bunch of ecryptfs utils, just type in your terminal &lt;code&gt;ecryptfs-&lt;/code&gt; and press tab to see the goodies:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;dex@nanosaurus ~&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ecryptfs&lt;/span&gt;&lt;span class="o"&gt;-&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;tab&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="n"&gt;ecryptfs&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;add&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;passphrase&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="n"&gt;ecryptfs&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="n"&gt;ecryptfs&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;insert&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;wrapped&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;passphrase&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;into&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;keyring&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="n"&gt;ecryptfs&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;manager&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Finally to initiate an ecryptfs system we need to run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ ecryptfs-setup-private --nopwcheck --noautomount
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We use &lt;code&gt;--nopwcheck&lt;/code&gt; and &lt;code&gt;--noautomount&lt;/code&gt; flags here for extra security.   &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;nopwcheck&lt;/code&gt; allows us to use different password from our user's password   &lt;/li&gt;
&lt;li&gt;&lt;code&gt;noautomount&lt;/code&gt; disables automatic encrypted system mounting, since it's also not a very great idea and wouldn't work if our encryption password is different from our user's one.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This command will ask you for &lt;code&gt;login&lt;/code&gt; and &lt;code&gt;mount&lt;/code&gt; passwords. For login password use your own password and for mount password leave it empty as ecryptfs will create it for you using your login password as a seed.&lt;/p&gt;
&lt;p&gt;Afterwards the system will be initiated and  &lt;code&gt;~/Private&lt;/code&gt; and &lt;code&gt;~/.ecryptfs&lt;/code&gt; directories will be created.  &lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: you want to backup your encrypted mount password which is located in &lt;code&gt;~/.ecryptfs/wrapped-passphrase&lt;/code&gt;. Remember that mount password is generated from your login password, so the passphrase is completely useless without your login password - put a copy of this file somewhere where you will never lose it!&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="usage"&gt;Usage&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;~/Private&lt;/code&gt; is your ecryptfs encrypted directory from now on. To use it you must mount it with &lt;code&gt;ecryptfs-mount-private&lt;/code&gt; command and once you're done using it use &lt;code&gt;ecryptfs-umount-private&lt;/code&gt; command to make it inaccessible once again:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;dex@~/Private $ ls
Access-Your-Private-Data.desktop  README.txt
$ ecryptfs-mount-private 
dex@~/Private $ cd .
dex@~/Private $ ls 
myetherwallet  btc diary emberassing_hobbies
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That's it! Now you have a safe directory on your computer where you can store sensitive data! Even if someone gets access to your user's homespace they'll still need your ecryptfs password to get anything out of it.  &lt;/p&gt;
&lt;h2 id="customizing"&gt;Customizing&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;~/Private&lt;/code&gt; is a pretty terrible name. It's Camelcase and I'd very much prefer it to be hidden.&lt;br/&gt;
We can change it to &lt;code&gt;~/.private&lt;/code&gt; very easily though:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;mv ~/Private ~/.private
echo /home/dex/.private &amp;gt; ~/.ecryptfs/Private.mnt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1 id="general-security-tips_1"&gt;General Security tips&lt;/h1&gt;
&lt;h2 id="the-three-rule"&gt;The Three Rule&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;If it doesn't exist in __three__ places it doesn't exist at all.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Fortunately ecryptfs directory acts like a normal directory so you can easily back it up to usb-stick, cloud storage or even email. Really just put that shit everywhere as longs as you have your login and mount passwords safe no one will be able to access your stuff.&lt;/p&gt;
&lt;p&gt;We've all heard magical stories about people losing usb sticks with their bitcoin wallets. Well if you had your wallet encrypted and in three different places that would have never happened!&lt;/p&gt;
&lt;h2 id="long-passwords-triumph"&gt;Long Passwords Triumph&lt;/h2&gt;
&lt;p&gt;I don't think I can top the explanation by this &lt;a href="https://xkcd.com/936/"&gt;xkcd comic&lt;/a&gt; so I'll just leave you with it and say that just have a simple a-z password which is at least 13 characters long.  &lt;/p&gt;
&lt;h1 id="further-reading_1"&gt;Further Reading&lt;/h1&gt;
&lt;p&gt;If you want to dig deeper I suggest arch-wiki &lt;a href="https://wiki.archlinux.org/index.php/ECryptfs"&gt;article&lt;/a&gt; or symply &lt;code&gt;man ecryptfs&lt;/code&gt;!
Additionally &lt;a href="https://stackoverflow.com/questions/tagged/ecryptfs?sort=votes&amp;amp;pageSize=15"&gt;top questions on stackoverflow&lt;/a&gt; also offer some interesting read.&lt;/p&gt;</content><category term="code"></category><category term="linux"></category><category term="guide"></category><category term="crypto"></category><category term="security"></category></entry><entry><title>wisdom.cat</title><link href="https://granitosaur.us/wisdom-cat.html" rel="alternate"></link><published>2017-04-01T00:00:00+02:00</published><updated>2017-04-01T00:00:00+02:00</updated><author><name>Bernardas Ališauskas</name></author><id>tag:granitosaur.us,2017-04-01:/wisdom-cat.html</id><summary type="html">&lt;p&gt;Recently I made small website in flask for aggregating educational youtube videos and I'd like to share my experience.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've always wanted to make a webapp and flask always looked appealing but deployment and production hassle really turned me off. A week ago I tried to force myself through it and I have to say, it's really not that bad!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is not a guide and you most likely won't learn anything - it's more of a condense story of how wisdom.cat was developed and doesn't really serve any other purpose&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="wisdom.cat" src="https://i.imgur.com/JppvDUZ.png"/&gt;&lt;/p&gt;
&lt;h2 id="wisdomcat"&gt;Wisdom.cat&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;$ cat wisdom&lt;br/&gt;
=( ^ &amp;gt;w&amp;lt; ^ )= &lt;br/&gt;
wisdom.cat - is a video aggregator website that aggregates bite size videos &lt;br/&gt;
which are in some way educational. The intention of this website is&lt;br/&gt;
to have something beneficial to watch during short down-time periods; &lt;br/&gt;
https://github.com/Granitosaurus/wisdom.cat  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I work remotely and I tend to eat at home so I usually watch a video or two during my lunch break. However I wanted to make sure I don't completely waste this time and don't let my brain fall a sleep or lose track so I've been watching education videos to compensate.&lt;br/&gt;
So the goal of &lt;a href="http://wisdom.cat"&gt;wisdom.cat&lt;/a&gt; is to solve this issue and provide me and others who are stuck in a similar position something to watch.  &lt;/p&gt;
&lt;h3 id="alternatives-and-inspirations"&gt;Alternatives and Inspirations&lt;/h3&gt;
&lt;p&gt;&lt;img alt="looking around" src="https://i.redd.it/1h2rkgvxzv6y.gif"/&gt; &lt;/p&gt;
&lt;p&gt;For quite a while a brilliant website (that wisdom.cat is more or less a clone of) performed this function - it's called http://unplugthetv.com. However as time went on the feed slowed down to the point where it has been completely dead since October of 2016.&lt;br/&gt;
I didn't like few things on that website; first that the feed was fairly slow and the second, much more important one, that there was no way to customize it. So a lot of unwanted things ended up in the feed, my personal example being the  TED channel, it used to be brilliant - 4 years ago, now it's utter garbage that if anything makes people more dumb instead of educating them.&lt;/p&gt;
&lt;p&gt;So in hindsight you could say wisdom cat was born from a dead cool website and my hatred for TED (TED-ED is really lovely though!).&lt;/p&gt;
&lt;p&gt;Some alternatives I came across while doing my research that are worth mentioning:&lt;br/&gt;
http://reddit.com/r/mealtimevideos - similar purpose, however it's not focused on education.&lt;br/&gt;
Curiousity app - I've never used it but heard one person on irc.#flask say that wisdom.cat is similar.  &lt;/p&gt;
&lt;h2 id="databases_1"&gt;Databases&lt;/h2&gt;
&lt;p&gt;&lt;img alt="wisdomcat database story" src="https://granitosaur.us/images/wisdomcat-db.png"/&gt; &lt;/p&gt;
&lt;p&gt;I didn't know much about designing webapp data storage at the time and just went with sqlite3, which didn't play well with pythonanywhere hosting that I was trying to use at the time. So I made a switch to mysql, which was a miserable  mistake. Mysql is dreadful to work with and I've spent more time working on the database and such than on the web app itself. &lt;br/&gt;
Frankly it was extremely unenjoyable experience and since this project was my spare time thing I came to a brilliant conclusion to try something different.   &lt;/p&gt;
&lt;p&gt;I got Redis! And honestly, it's been a complete pleasure starting the very moment I ran &lt;code&gt;pacman -S redis&lt;/code&gt;.&lt;br/&gt;
So what's so great about it? Well it might sound clich&amp;egrave; but It Just Works&amp;trade;, no really after I installed it via pacman, started the daemon via systemctl and got a python api (python-redis or flask-redis) I had to add pretty much 2 lines to my youtube scraper to store the data and 4 lines to my flask views to display it - it was liberating!  &lt;/p&gt;
&lt;p&gt;One important thing I learned when it comes to Redis is to "store data the same way you will query it". Since redis doesn't really have a full query mechanism behind it, you might end up stuck without being able to get your data without a bunch of python code to help you out.&lt;br/&gt;
My first attempt was to write every video to &lt;code&gt;video_&amp;lt;id&amp;gt;&lt;/code&gt; but since I wanted wisdom.cat to have filters for channels that didn't end up being as efficient. Nowhere near of being a huge performance hit but in short time it might become noticeable.&lt;br/&gt;
Then I just wrote video data to Redis lists named after channels, i.e. "channel_CGP Grey" will contain a list of videos, that can be updated and modified - which solved this non-issue completely!  &lt;/p&gt;
&lt;h2 id="flask"&gt;Flask&lt;/h2&gt;
&lt;p&gt;Flask was an easy choice for wisdom.cat; it was popular(read a lot of learning resources), light-weight and in python!&lt;br/&gt;
I'm not sure if I have much to add other than few struggles I had as a newb.&lt;br/&gt;
The circular imports got me a time or two, because for some reason I decided to keep &lt;code&gt;app&lt;/code&gt; object in &lt;code&gt;__init__.py&lt;/code&gt; and ended up kinda liking it.  &lt;/p&gt;
&lt;p&gt;The biggest issue I had was the sessions. Initially wisdom cat would generate video queue and channel subscriptions and put them to flask session, aka secure cookie. Now this brought an impossible to debug issue that made be dippy few times. Turns out the cookie/session ended up being so big it would not update anymore appropriately since cookie has 4k byte limit and the wisdom cat setup, at the time with 30 videos, was already reaching that!&lt;br/&gt;
So needless to say it was a bad idea from the very get go, fortunately with redis up and running it was no longer an issue that would be difficult to solve.&lt;/p&gt;
&lt;p&gt;All in all it's hard to say something original about Flask since it has such a huge community and so many opinions, research and information already that anything I'd say would be awfully redundant so I'll just say that Flask was a real pleasure to work with and honestly, I can't wait to start a new project already!  &lt;/p&gt;
&lt;h2 id="deploying"&gt;Deploying&lt;/h2&gt;
&lt;p&gt;This was my most dreaded bit. I've been playing with servers to run crawlers and some notification tools for some time but I've never exposed them to public or made them accessible.&lt;br/&gt;
Fortunately it all sounds more complicated than it actually is. All you need is an application, &lt;a href="https://en.wikipedia.org/wiki/Web_Server_Gateway_Interface"&gt;wsgi&lt;/a&gt; tool, some daemons and an afternoon!  &lt;/p&gt;
&lt;p&gt;I ended up renting a &lt;a href="https://www.linode.com/?r=37112fcbb52a0f3c556a91b37d215d72e4ef5702"&gt;linode&lt;/a&gt; (which is really lovely!) and using arch node with systemd, nginx and gunicorn. &lt;br/&gt;
I've been using arch and systemd for few years now so this part was a breeze, however nginx gave me a bit of trouble. Mostly because most of the tutorials, guides and documentation assumed that it's running on Ubuntu which for some reason uses unusual structure and stores servers in &lt;code&gt;sites-enabled&lt;/code&gt; and &lt;code&gt;sites-available&lt;/code&gt; which were not present in arch(or most of the other distros). Apparently this was to replicated some apache server structure so I decided to join the cool kid's club and replicate the same structure with &lt;code&gt;inludes&lt;/code&gt; and system links - it worked out perfectly fine and I with few lines of configs it was working.&lt;/p&gt;
&lt;p&gt;Finally I wrote up two daemon services: &lt;code&gt;gunicorn&lt;/code&gt; wsgi service to launch and expose the app, and a timer service - to run video crawler every half an hour, to update the video feed.&lt;/p&gt;
&lt;p&gt;And with that wisdom.cat was finally drawing it's first breaths! &lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This was a really fun little project and I highly recommend investing some time into learning how to make a simple web app like this - it opens up so many gates for so many opportunities!&lt;/p&gt;</content><category term="code"></category><category term="python"></category><category term="linux"></category><category term="flask"></category><category term="webdev"></category></entry><entry><title>Doom and Stone in Tallinn</title><link href="https://granitosaur.us/doom-tallinn-2017.html" rel="alternate"></link><published>2017-01-31T00:00:00+01:00</published><updated>2017-01-31T00:00:00+01:00</updated><author><name>Bernardas Ališauskas</name></author><id>tag:granitosaur.us,2017-01-31:/doom-tallinn-2017.html</id><summary type="html">&lt;p&gt;I went to a brilliant doom and stone metal concert in Tallinn, Estonia. Spearheaded by Stoned Jesus itself and back by two brilliant bands from Poland!&lt;/p&gt;</summary><content type="html">&lt;p&gt;I'm a big fan of doom metal and to my surprise a brilliant rock club "Woodstock" was holding a concert featuring 3 brilliant bands: &lt;a href="https://dopelord.bandcamp.com/"&gt;Dopelord[PL]&lt;/a&gt;, &lt;a href="https://sunnataofficial.bandcamp.com/"&gt;Sunnata[PL]&lt;/a&gt; and &lt;a href="https://stonedjesus.bandcamp.com/"&gt;Stoned Jesus[UA]&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="The Procession II" src="https://granitosaur.us/images/doom-tallinn.png"/&gt;&lt;/p&gt;
&lt;p&gt;I've attended Stoned Jesus concert last year at the same place and it was a blast, so even though I wasn't familiar with the other two bands, Stoned Jesus was more than enough to convince me this time.&lt;br/&gt;
I tried to keep the other two bands a surprise and only caved during the long and tedious bus ride to the concert itself. I've listened to the top 3 songs on spotify and was completely stoked! &lt;/p&gt;
&lt;p&gt;The club &lt;a href="http://www.rockstars.ee/?s=woodstock"&gt;Woodstock&lt;/a&gt; is pretty small and was totally packed throughout the entirety of the concert, which started at 8:30 and went a bit over midnight, so that's almost 4 hours of hard metal music from three very distinct and brilliant bands! &lt;br/&gt;
We had not only Estonian fans, but also Fins, Russians and even Swedes there. Looking back, I'm very surprised how the club managed to fit in so many people.&lt;/p&gt;
&lt;p&gt;The band that opened up was &lt;a href="https://dopelord.bandcamp.com/"&gt;Dopelord&lt;/a&gt;, a doom/stone metal band from Poland that was founded back in 2010. They're probably the most doom-like band of the three bands and I couldn't help but feel reminded of my favorite band Electric Wizard which just like Dopelord took inspiration from old movies and witchcraft.&lt;/p&gt;
&lt;iframe allowfullscreen="" class="center-block" frameborder="0" height="360" src="https://www.youtube.com/embed/cLyN2EKqbks" title="favorite song of the show" width="560"&gt;&lt;/iframe&gt;
&lt;p&gt;The second band was &lt;a href="https://sunnataofficial.bandcamp.com/"&gt;Sunnata&lt;/a&gt;, which is also from Poland.&lt;br/&gt;
Sunnata formed in 2008 and overall they sound very much doom, though maybe a bit more on the upbeat side, which is not a bad thing by any means, in fact it was very fitting pacing wise for this particular show. &lt;/p&gt;
&lt;iframe allowfullscreen="" class="center-block" frameborder="0" height="360" src="https://www.youtube.com/embed/DoEIzJaFA1c" title="Favorite sunnatra song" width="560"&gt;&lt;/iframe&gt;
&lt;p&gt;As a side note I'd like to mention the guitarist's wicked-fabulous hair that had a dedicated fan for full potential. &lt;/p&gt;
&lt;p&gt;&lt;img alt="Sunnata's guitarist" src="https://granitosaur.us/images/sunnata_hair.jpg"/&gt;&lt;/p&gt;
&lt;p&gt;The third and final band was the star of the show - &lt;a href="https://stonedjesus.bandcamp.com/"&gt;Stoned Jesus&lt;/a&gt;. Definitely the most known band in the show and one of the more known recent bands in the stone rock/metal genre. Some people would disagree with me regarding doom metal(or metal in general) classification, but there are definitely noticeable elements in songs like &lt;a href="https://youtu.be/kKAWz4bIeLg?t=286"&gt;Electric Mistress&lt;/a&gt; and it's especially noticeable during live shows!  &lt;/p&gt;
&lt;p&gt;It was a 5-year anniversary of their most well known album Seven Thunders Roar and the band was very sincere and thankful to the fans for the support throughout the years and to quote the vocalist/guitarist Igor Sydorenko: "5 years ago we played in crappy places like this, but with 10 times less people, so thank you for the support!"&lt;br/&gt;
To finish up the show the band played a 20 minute long encore and had a pretty successful stage dive to finish up already exhausted fans!&lt;/p&gt;
&lt;iframe allowfullscreen="" class="center-block" frameborder="0" height="360" src="https://www.youtube.com/embed/iW1jxJ6ISks" title="I'm the mountain!" width="560"&gt;&lt;/iframe&gt;
&lt;p&gt;To summit it - the show was great and I'd like to publicly thank the bands for coming out this far up to Estonia, Tallinn and delivering such a great show! And would highly recommend, given the opportunity, to check these bands out to anyone both live and online.  &lt;/p&gt;
&lt;p&gt;That's pretty much it, a rather pointless blog but I felt really inspired to share this and publicly thank Dopelord, Sunnata and Stoned Jesus for a great show!&lt;/p&gt;</content><category term="self"></category><category term="metal"></category><category term="doom"></category><category term="tallinn"></category></entry><entry><title>Getting Terminal Size In Python</title><link href="https://granitosaur.us/getting-terminal-size.html" rel="alternate"></link><published>2017-01-26T00:00:00+01:00</published><updated>2017-01-26T00:00:00+01:00</updated><author><name>Bernardas Ališauskas</name></author><id>tag:granitosaur.us,2017-01-26:/getting-terminal-size.html</id><summary type="html">&lt;p&gt;Getting terminal size can be vital for your application, especially if you are doing some serious printing or drawing. There are some few tricks worth noting that I'd like to share.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Getting terminal size can be vital for your application, especially if you are doing some serious printing or drawing. However there are some few tricks worth noting that I'd like to share in this blog post.&lt;/p&gt;
&lt;p&gt;First of all the recommended way or the "pythonic" way of retrieving terminal size for python3 is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/usr/bin/env python3&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;shutil&lt;/span&gt;
&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;shutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_terminal_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fallback&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And it works pretty great, well for the most part that is.  &lt;/p&gt;
&lt;h2 id="the-issue"&gt;The Issue&lt;/h2&gt;
&lt;p&gt;This particular function is just a high level wrapper around low level cpython function &lt;code&gt;os.get_terminal_size&lt;/code&gt; and the only real thing it does is handle an exception and returns &lt;code&gt;fallback&lt;/code&gt; values if that's the case.&lt;/p&gt;
&lt;p&gt;However there's a huge pitfall with this function and it's that &lt;strong&gt;it doesn't work with terminal pipes!&lt;/strong&gt; &lt;br/&gt;
To confirm and test that with we can try this simple script &lt;code&gt;size.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;shutil&lt;/span&gt;

&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;shutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_terminal_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fallback&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;456&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'cols:&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;rows:&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ python size.py
cols:89
rows:22
$ python size.py &lt;span class="p"&gt;|&lt;/span&gt; cat
cols:123
rows:456
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;89 to 22 is my actual size however when piping to anything, including python itself, the size seems to fallback to the fallback values, which in most cases defeats the whole purpose of retrieving the terminal size.&lt;/p&gt;
&lt;h3 id="making-it-work"&gt;Making It Work!&lt;/h3&gt;
&lt;p&gt;The solution is pretty simple - use the other function instead!&lt;br/&gt;
If we use &lt;code&gt;os.get_terminal_size(0)&lt;/code&gt; function, we'll get it working with piping too!&lt;/p&gt;
&lt;p&gt;To test that lets change our script:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/usr/bin/env python3&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;

&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_terminal_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'cols:&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;rows:&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The thing to note here is the positional argument &lt;code&gt;0&lt;/code&gt; in &lt;code&gt;os.get_terminal_size()&lt;/code&gt; function, which tells which &lt;a href="https://en.wikipedia.org/wiki/File_descriptor"&gt;file descriptor&lt;/a&gt; to use: &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;0 - Standard Input&lt;br/&gt;
1 - Standard Output&lt;br/&gt;
2 - Standard Error  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;By default both &lt;code&gt;os&lt;/code&gt; and &lt;code&gt;shutil&lt;/code&gt; functions use &lt;code&gt;1&lt;/code&gt;, which stands for Standard Output. This means that if we pipe and this output detaches itself we get an OsError:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ne"&gt;OSError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Errno&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;Inappropriate&lt;/span&gt; &lt;span class="n"&gt;ioctl&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This default somewhat makes sense if the size of your terminal changes when the output is displayed, but I'm having trouble even imagining an example where that would be the case.&lt;/p&gt;
&lt;p&gt;So if we were to run our script now, we'd get the results we are looking for:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ python size.py
cols:89
rows:22
$ python size.py &lt;span class="p"&gt;|&lt;/span&gt; cat
cols:89
rows:22
&lt;span class="c1"&gt;# Even if we pipe multiple times!&lt;/span&gt;
$ python size.py &lt;span class="p"&gt;|&lt;/span&gt; grep . &lt;span class="p"&gt;|&lt;/span&gt; cat
cols:89
rows:22
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="edit_1"&gt;Edit&lt;/h2&gt;
&lt;p&gt;As user bearded_unix_guy pointed out on &lt;a href="https://www.reddit.com/r/Python/comments/5q7b36/getting_terminal_size_in_python/dcxil66/"&gt;reddit&lt;/a&gt;, using stdin(argument 0) might not always work, in particular it wont work when we pipe to our app: &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In case like above we actually want to use stdout(default argument) since it's not detached. However what about if your app is in the middle: &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In this case neither stdout nor stdin will work, but sterr(2) will!&lt;br/&gt;
So to combine all of these to cover all of the cases we can simply wrap it in a for loop with exception catching:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_terminal_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fallback&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_terminal_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;OSError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# set default if the loop completes which means all failed&lt;/span&gt;
        &lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fallback&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And there you go, you can use that instead of &lt;code&gt;os.get_terminal_size()&lt;/code&gt; and have a pipe-foolproof terminal size getter! &lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I've spent more time than I'd like to admit trying to figure this out and the &lt;a href="http://stackoverflow.com/a/41864359/3737009"&gt;stackoverflow thread&lt;/a&gt; for this subject only left me more confused.&lt;br/&gt;
As always if you have any questions, critique or notices feel free to leave a comment!&lt;/p&gt;</content><category term="code"></category><category term="python"></category><category term="linux"></category><category term="guide"></category></entry><entry><title>My Bin: center</title><link href="https://granitosaur.us/my-bin-center.html" rel="alternate"></link><published>2017-01-18T00:00:00+01:00</published><updated>2017-01-18T00:00:00+01:00</updated><author><name>Bernardas Ališauskas</name></author><id>tag:granitosaur.us,2017-01-18:/my-bin-center.html</id><summary type="html">&lt;p&gt;"My bin" is series of blog post where I cover some useful or at least interesting programs, scripts or hacks that I have in my ~/bin. In this one I cover a small pipeable app that simply centers text to your terminal size.&lt;/p&gt;</summary><content type="html">&lt;p&gt;People are naturally lazy and strive to automate as much as possible. I'm no exception and my user scripts directory &lt;code&gt;~/bin&lt;/code&gt; is full of scripts that make my life easier or at least makes me feel that is. &lt;br/&gt;
On how to setup your environment for easily accessible scripting see my &lt;a href="/python-scripts.html"&gt;Guide: setup for python scripting&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;Today I want to show you and explain some bits of a little python script for centering text. It's isn't particularly special, but it's a great base to show off how awesome and powerful python command line tools can be!&lt;br/&gt;
It's a simple pipeable script that takes in some text, centers it according to your terminal size and outputs it to standard output.My use case for this is for reading poems and by default they are not centered properly. To add to that I also like to constantly resize my terminal window because I'm running &lt;a href="http://i3wm.org/"&gt;i3wm&lt;/a&gt; - a tilling windows manager for linux, which forces every window to use up all of the space it can, which makes them very much dynamic and unpredictable.&lt;/p&gt;
&lt;p&gt;In other words it turns something like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ cat raven.md
Once upon a midnight dreary, &lt;span class="k"&gt;while&lt;/span&gt; I pondered, weak and weary,
Over many a quaint and curious volume of forgotten lore,
While I nodded, nearly napping, suddenly there came a tapping,
As of some one gently rapping, rapping at my chamber door.
&lt;span class="s1"&gt;'Tis some visitor,'&lt;/span&gt; I muttered, &lt;span class="s1"&gt;'tapping at my chamber door-&lt;/span&gt;
&lt;span class="s1"&gt;Only this, and nothing more.'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Into:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ cat raven.md &lt;span class="p"&gt;|&lt;/span&gt; center
             Once upon a midnight dreary, &lt;span class="k"&gt;while&lt;/span&gt; I pondered, weak and weary,              
                Over many a quaint and curious volume of forgotten lore,                 
             While I nodded, nearly napping, suddenly there came a tapping,              
               As of some one gently rapping, rapping at my chamber door.                
              &lt;span class="s1"&gt;'Tis some visitor,'&lt;/span&gt; I muttered, &lt;span class="s1"&gt;'tapping at my chamber door-               &lt;/span&gt;
&lt;span class="s1"&gt;                              Only this, and nothing more.'&lt;/span&gt;   
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;While taking your current terminal size into account, so no matter what you're doing or where you are the text will nice and pretty.   &lt;/p&gt;
&lt;h3 id="source"&gt;Source&lt;/h3&gt;
&lt;p&gt;Now lets take a look at the source code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="c1"&gt;#!/usr/bin/env python3&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;click&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;


    &lt;span class="nd"&gt;@click&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nd"&gt;@click&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'input'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nd"&gt;@click&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'output'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nd"&gt;@click&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'-l'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'--length'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'maximum line length [default:current terminal size]'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cli&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sd"&gt;"""Simple, pipeable tool for centering text"""&lt;/span&gt;
        &lt;span class="n"&gt;columns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_terminal_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;#1&lt;/span&gt;
        &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readlines&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;input&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdin&lt;/span&gt;  &lt;span class="c1"&gt;#2&lt;/span&gt;
        &lt;span class="n"&gt;_format&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'{{:^&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;}}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;#3&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;#4&lt;/span&gt;
            &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_format&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"__main__"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;cli&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="explanation"&gt;Explanation&lt;/h3&gt;
&lt;p&gt;I love &lt;code&gt;click&lt;/code&gt; library, which is a tool for creating command line interfaces. It's beautiful, easy and saves so much space and time. So we start off with two positional arguments for input and output filenames, these are not necessary for the pipe logic we need but is a nice addition if there's a need for standalone function and only takes two extra lines, so why not!  &lt;br/&gt;
Next we have custom option for length which allows overriding maximum line length. In case you have a very huge terminal window and you just want a nice margin instead of the text being at the very center of your screen.
Finally there's the program itself:&lt;br/&gt;
&lt;code&gt;#1&lt;/code&gt; - We retrieve dimensions of the current terminal window. This returns a tuple of &lt;code&gt;(columns, rows)&lt;/code&gt; since we only care about columns we take the first member. &lt;br/&gt;
Check out a related blog entry why we are using this function instead of alternatives &lt;a href="/getting-terminal-size.html"&gt;here&lt;/a&gt;&lt;br/&gt;
&lt;code&gt;#2&lt;/code&gt; - We decide on which source to use for input, if first position argument is supplied to script, we'll use that as a source, otherwise use standard input.&lt;br/&gt;
&lt;code&gt;#3&lt;/code&gt; - This might appear complicated but what we are doing here is creating a format that we will use to format every line of our text. The line evaluates to &lt;code&gt;{:^&amp;lt;terminal_size&amp;gt;}\n&lt;/code&gt; now if we call &lt;code&gt;.format()&lt;/code&gt; on that we can insert text and it will be centered. For more check out &lt;a href="https://docs.python.org/3.1/library/string.html#string-formatting"&gt;python's string formatting&lt;/a&gt;, it's awesome!&lt;br/&gt;
&lt;code&gt;#4&lt;/code&gt; - And lastly we have the loop itself. Here we loop through every line, center it and either write it to file if the second positional argument is supplied or put it straight to standard output.  &lt;/p&gt;
&lt;h3 id="improvements"&gt;Improvements&lt;/h3&gt;
&lt;p&gt;You could probably go wild with a bunch of flags and modifications but it's important to remember to KISS - keep it simple stupid. With pipes, aliases and various other shortcuts leaving this script to do one job is very much a good idea! :) &lt;/p&gt;
&lt;h3 id="conclusion"&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;I'd like to encourage anyone who uses ugly awk scripts and aliases just write a short command line application with python and &lt;code&gt;click&lt;/code&gt;. It takes no longer than 10 minutes, it's beautiful, usable, readable and easily shareable!&lt;br/&gt;
Let me know if you have any questions and stay tuned for more scripts and explanations!&lt;/p&gt;</content><category term="code"></category><category term="python"></category><category term="linux"></category><category term="guide"></category><category term="my-bin"></category></entry><entry><title>Guide: setup for python scripting</title><link href="https://granitosaur.us/python-scripts.html" rel="alternate"></link><published>2017-01-17T00:00:00+01:00</published><updated>2017-01-17T00:00:00+01:00</updated><author><name>Bernardas Ališauskas</name></author><id>tag:granitosaur.us,2017-01-17:/python-scripts.html</id><summary type="html">&lt;p&gt;Short guide on how to setup an environment for executable python scripts&lt;/p&gt;</summary><content type="html">&lt;p&gt;Few python scripts can make your daily routines significantly easier and enjoyable. In this post I'll cover how to set up your linux environment to have your scripts always easily accessible.&lt;br/&gt;
In other words, how to make this reality:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ amicool 
Yes you are, keep it up!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To start off we need to create a directory where your scripts be located. A de facto standard is &lt;code&gt;~/bin&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ mkdir bin
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="handling-the-path"&gt;Handling the PATH&lt;/h2&gt;
&lt;p&gt;Now depending on your system and the shell you use, this directory might already be created or might not be in your &lt;code&gt;PATH&lt;/code&gt; environment at all.&lt;br/&gt;
&lt;code&gt;PATH&lt;/code&gt; environment variable is a list of locations your shell will look for executable files - programs in other words. So if you're using good ol' bash simply adding this line to your &lt;code&gt;~/.bashrc&lt;/code&gt; file will do:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="n"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"~/bin:$PATH"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="creating-the-script"&gt;Creating the script&lt;/h2&gt;
&lt;p&gt;Now we can create the script itself:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ touch ~/bin/amicool 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Notice that we do not use &lt;code&gt;.py&lt;/code&gt; extension, because linux takes executables in PATH literally and we don't want to type the extension whenever we call the script.&lt;br/&gt;
We should populate this script with some actual code:&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt;1&lt;/span&gt;
&lt;span class="normal"&gt;2&lt;/span&gt;
&lt;span class="normal"&gt;3&lt;/span&gt;
&lt;span class="normal"&gt;4&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/usr/bin/env python3&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'__main__'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Yes you are, keep it up!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Notice that we start of the file with &lt;code&gt;#!/usr/bin/env python3&lt;/code&gt;. This is called shebang, it's basically a header that tells your operating system what to use to execute the file, in this case we want to use &lt;code&gt;python3&lt;/code&gt;.&lt;br/&gt;
Afterwards we have the most simple of python code that just prints some text when call it directly, you see when you call a python module directly the magic variable &lt;code&gt;__name__&lt;/code&gt; is being set to &lt;code&gt;'__main__'&lt;/code&gt; so you can gate some code in your script behind it so it would only be executed if it's called directly as opposed by being imported by some other module.  &lt;/p&gt;
&lt;h2 id="making-it-executable"&gt;Making it executable&lt;/h2&gt;
&lt;p&gt;Lastly we want to mark our file "executable" so the operating system know it can actually call this file as a script or a program:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;chmod +x ~/bin/amicool
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And there we go! Your script is good to go and whenever you feel inadequate regarding your coolness just type:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ amicool
Yes you are, keep it up!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="code"></category><category term="python"></category><category term="linux"></category><category term="guide"></category></entry><entry><title>How to get scrapy help.</title><link href="https://granitosaur.us/scrapy-help.html" rel="alternate"></link><published>2016-10-30T00:00:00+02:00</published><updated>2016-10-30T00:00:00+02:00</updated><author><name>Bernardas Ališauskas</name></author><id>tag:granitosaur.us,2016-10-30:/scrapy-help.html</id><summary type="html">&lt;p&gt;Few suggestions how to ask questions correctly and where to ask them regarding using scrapy web-crawling framework.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Scrapy is a web-scraping framework for python. It's pretty popular and at the moment of writing it has over 16000 stars &lt;a href="https://github.com/scrapy/scrapy"&gt;on github&lt;/a&gt;. In terms of codebase scrapy is pretty simple, however there are few things that are not as explicit as they could be in favor of abstraction and development simplicity.&lt;br/&gt;
Not to mention millions of websites that provide their own unique scraping challenges.  &lt;/p&gt;
&lt;p&gt;So if you do end up not understanding something or encountering some of the few scrapy's quirks, how do you go about it?&lt;/p&gt;
&lt;h1 id="stackoverflow-guidelines"&gt;Stackoverflow Guidelines&lt;/h1&gt;
&lt;p&gt;First thing you should do is read is &lt;a href="http://stackoverflow.com/help/how-to-ask"&gt;&lt;em&gt;how to ask a good question on stackoverflow&lt;/em&gt;&lt;/a&gt;. &lt;br/&gt;
It's a brilliant guide by, without a doubt the biggest Q&amp;amp;A website on the web, and it focuses on how to ask a good questions regardless of the topic. Following these guidelines not only make it easy for people to help you but also easy for you, yourself to formulate your question and understand the issue you are facing!&lt;/p&gt;
&lt;h1 id="where-to-get-help"&gt;Where To Get Help?&lt;/h1&gt;
&lt;p&gt;There are two places you can go to with your scrapy related questions and issues:  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="http://stackoverflow.com/questions/tagged/scrapy"&gt;Stackoverflow&lt;/a&gt;. &lt;br/&gt;
The issue with Stackoverflow is that it has a general rule of questions having to be generic, that means asking how to get price on this item on amazon is not a fit question. However the user base on &lt;code&gt;scrapy&lt;/code&gt; tag seems to be quite understanding of this and tend to be quite lenient with reports and down-votes, but don't be surprised if your post gets down-voted or put on hold. All you can do is to try and make your issue more generic and hope for the best!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;IRC! @ irc.freenode.org #scrapy &lt;br/&gt;
Good old IRC has been there for decades and even though it dropped in popularity quite significantly, it's still a great place to get help on any subject and scrapy is not an exception. 
Feel free to join the channel and ask questions about anything scrapy related; you can find me there too!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://support.scrapinghub.com/forums/1-general/"&gt;Scrapinghub Forums&lt;/a&gt;&lt;br/&gt;
Scrapinghub is the company behind scrapy and they have a user forum, so naturally it's a great place to look for help when it comes to your scrapy issues!  &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reddit&lt;br/&gt;
There's an official &lt;a href="https://reddit.com/r/scrapy"&gt;scrapy subreddit&lt;/a&gt;, which isn't very active but I can tell you for a fact that a lot of people that are involved with scrapy keep an eye on it. It's a great place for some discussions that might not fit stackoverflow and irc.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="providing-information"&gt;Providing Information&lt;/h1&gt;
&lt;p&gt;To debug an issue and get the help you need you need to provide information about your problem:  &lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Source Code of your spider, settings.py and pipelines.py files.&lt;/li&gt;
&lt;li&gt;Website you are crawling - sometimes people refrain from providing the url in fear of legal issues or some judgment. Don't worry about that, scraping is very much legal and no one will judge you, it might very well be the opposite - people might be more keen to help you scrape some weird porn website than amazon.  &lt;/li&gt;
&lt;li&gt;Crawl Log (see &lt;a href="#log"&gt;Producing Logs&lt;/a&gt;) - Scrapy logs majority of the events that happen in your spider, so to debug your spider the best resources are these logs.  &lt;/li&gt;
&lt;li&gt;Spider Output (see &lt;a href="#output"&gt;Producing Output&lt;/a&gt;) - This will rarely be useful for anyone else but yourself, but it can be very useful in some cases.  &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once you have these bits you can easily formulate your question and I'm sure someone will help you out!&lt;/p&gt;
&lt;h2 id="log"&gt;Producing Logs&lt;/h2&gt;
&lt;p&gt;To save a log of your spider run you can use UNIX output redirection syntax:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;scrapy crawl myspider 2&amp;gt;&amp;amp;1 &amp;gt; mylog.log
# or
scrapy crawl myspider &amp;amp;&amp;gt; mylog.log
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Explanation:&lt;br/&gt;
    1. &lt;code&gt;scrapy crawl myspider&lt;/code&gt; - is a scrapy command that will start crawling spider called &lt;code&gt;myspider&lt;/code&gt;&lt;br/&gt;
    2. &lt;code&gt;2&amp;gt;&amp;amp;1&lt;/code&gt; - is UNIX syntax for redirecting error output to standard output. In UNIX there are types of outputs and in your log you want to have both of them in one file.&lt;br/&gt;
    3. &lt;code&gt;&amp;gt; mylog.log&lt;/code&gt; - is another UNIX output redirection, but this time we redirect the output to file called &lt;code&gt;mylog.log&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Tip: points 2 and 3 can be summarized as &lt;code&gt;&amp;amp;&amp;gt;&lt;/code&gt; in bash version 4 and up&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For logging scrapy uses python's built-in &lt;a href="https://docs.python.org/3/library/logging.html"&gt;&lt;code&gt;logging&lt;/code&gt; module&lt;/a&gt; which by itself is pretty awesome! If you look into it, it might appear quite daunting but you can actually just &lt;code&gt;import logging&lt;/code&gt; and simply log message to root logger: &lt;code&gt;logging.warning("this page has no next page")&lt;/code&gt;. To have simple logging in your spider.&lt;/p&gt;
&lt;h2 id="output"&gt;Producing Output&lt;/h2&gt;
&lt;p&gt;Scrapy can automatically produce output in one these formats:  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;'xml', 'jsonlines', 'jl', 'json', 'csv', 'pickle', 'marshal'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To do that simply run &lt;code&gt;crawl&lt;/code&gt; command with &lt;code&gt;--output&lt;/code&gt; flag (&lt;code&gt;-o&lt;/code&gt; for short version) and provide a name + file ending of format you want as an argument:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;scrapy crawl myspider --output output.json
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;This will output all items your spider spews out to &lt;code&gt;output.json&lt;/code&gt; file.&lt;/em&gt; &lt;/p&gt;
&lt;p&gt;To get help for readability purposes you probably want to use either &lt;code&gt;json&lt;/code&gt; or &lt;code&gt;xml&lt;/code&gt; since those are most readable and as described in section below parsing-friendly formats.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Tip: You can actually tell scrapy to produce output to stdout directly by setting output argument to &lt;code&gt;-&lt;/code&gt;:&lt;/em&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;scrapy crawl myspider -t json -o - output.json
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="inspecting-output"&gt;Inspecting Output&lt;/h2&gt;
&lt;p&gt;There few tools to parse &lt;code&gt;json&lt;/code&gt; or &lt;code&gt;xml&lt;/code&gt; content, similar like you'd use &lt;code&gt;sed&lt;/code&gt; or &lt;code&gt;grep&lt;/code&gt; in unix. The most popular and widely known is probably &lt;a href="https://stedolan.github.io/jq/"&gt;jq&lt;/a&gt;, which I believe translates to json query.&lt;br/&gt;
I personally really dislike that jq uses it's own mini-language as opposed to xpath or css selectors we all know, love and use daily.&lt;br/&gt;
So in response to this I made &lt;a href="https://github.com/granitosaurus/pq/"&gt;&lt;strong&gt;PQ&lt;/strong&gt;&lt;/a&gt;! It uses xpath and css selectors as well as support both json and xml parsing.&lt;/p&gt;
&lt;p&gt;To put it shortly, using the tools described above you can find specific values of some fields really easily.&lt;br/&gt;
Lets imagine we have a bunch of products that have these fields: name and price. Now for some reason Samsung items have weird pricing and we want to find out whether that's the case every time we update the code. &lt;/p&gt;
&lt;p&gt;For example using pq we can navigate the prices of items that have some keywords in their names:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;"//item[contains(@name,'samsung')]/price/text()"&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Will find all items that contain "samsung" in the name and output their price values. If you change up your spider an run this command again you can easily navigate whether the values are changing.&lt;/p&gt;
&lt;p&gt;You can combine this with scrapy spider redirection to have everything in one line:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;scrapy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;crawl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;spider&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;--nolog -t json -o - | pq "//item[contains(@name,'samsung')]/price/text()"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1 id="conclusion_1"&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Scrapy is a lovely framework and web-crawling is a tricky subjects with a lot of hidden issues, quirks and complexities. Because of it being rather big subjects and every spider having it's own challenges it might be difficult to find help. However I feel if you follow the steps and ideas described in this blog post you'll have a really good chance at getting some help either on stackoverflow or irc!&lt;/p&gt;
&lt;p&gt;Do you have any places where you go to with your scrapy or web-crawling related questions? Did I miss something important? Leave the comment below :)&lt;/p&gt;</content><category term="code"></category><category term="scrapy"></category><category term="python"></category><category term="stackoverflow"></category><category term="web-crawling"></category></entry><entry><title>Pycon PL + Warsaw report</title><link href="https://granitosaur.us/pycon-pl-after.html" rel="alternate"></link><published>2016-10-23T00:00:00+02:00</published><updated>2016-10-23T00:00:00+02:00</updated><author><name>Bernardas Ališauskas</name></author><id>tag:granitosaur.us,2016-10-23:/pycon-pl-after.html</id><summary type="html">&lt;p&gt;Pycon Poland and Warsaw aftermath: a summary of my experience in Pycon Poland and Poland, Warsaw in general.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Last year during Euro-Python 2015 someone from PyconPL had an interesting lightning talk about PyconPL, and since that moment I was sold. However I missed it by few days that year. However this year I made it and as a bonus I checked out Warsaw, Poland too!&lt;/p&gt;
&lt;p&gt;You can check my previous blog post how we got to Warsaw &lt;a href="/pycon-pl.html"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Before going to Pycon me and my girlfriend took two days to look around Warsaw, Poland. Throughout the trip we stayed in 3 airbnb places. The first day we were really surprised how pretty and big Warsaw is. To give a little background, before the trip we did a bunch of research of what to do in Warsaw and we really didn't find much so we didn't have high hopes to begin with, thus the surprise. &lt;/p&gt;
&lt;p&gt;So without further ado, here is what I (and in some cases we) think of Warsaw!&lt;/p&gt;
&lt;h2 id="food-and-prices"&gt;Food and Prices&lt;/h2&gt;
&lt;p&gt;The city is beautiful and there are so &lt;strong&gt;many restaurants and cafes&lt;/strong&gt;. The first thing we did was go to &lt;a href="https://www.tripadvisor.com/Restaurant_Review-g274856-d8529497-Reviews-Mango_Vegan_Street_Food-Warsaw_Mazovia_Province_Central_Poland.html"&gt;Mango Vegan Street Food&lt;/a&gt; which serves great hummus as well as other vegan dishes at a surprisingly low price, brilliant little place!   &lt;/p&gt;
&lt;p&gt;Low price is definitely a recurring theme in Warsaw - everything is surprisingly cheaper than in Estonia or Lithuania.&lt;br/&gt;
I've heard of a saying: "Poland is an eastern-european country with western-european prices" which I heavily disagree with, unless western-europe is cheaper than eastern in this context. Everything in general is at least 20% cheaper than in Estonia, sometimes significantly more and quality doesn't suffer because of it. The selection of foods and products in general is huge too - one of the benefits of being in central Europe I guess.&lt;/p&gt;
&lt;p&gt;As a vegetarian I was surprised by how &lt;strong&gt;many vegetarian places&lt;/strong&gt; and options there are in Warsaw. The vegan trend definitely feels real, which was both a pleasant surprise and a piece of convenience for myself.  &lt;/p&gt;
&lt;blockquote&gt;
&lt;dl&gt;
&lt;dt&gt;Simple Definition of meat&lt;/dt&gt;
&lt;dd&gt;the flesh of an animal used as food&lt;/dd&gt;
&lt;dd&gt;a type of meat&lt;/dd&gt;
&lt;dd&gt;the part of something (such as a nut) that can be eaten&lt;/dd&gt;
&lt;/dl&gt;
&lt;/blockquote&gt;
&lt;p&gt;As a side note for vegetarianism itself - I found that a lot of polish people do not consider fish to be meat, which is extremely weird and nonsensical. I've noticed that it's a quite common misunderstanding across Europe and had a very similar thing happen in Spain during my visit for Europython 2015 when the vegetarian sandwich I ordered contained tuna...&lt;/p&gt;
&lt;p&gt;Also worth noting that a &lt;strong&gt;vegetarian person who eats fish is actually called pescatarian&lt;/strong&gt;. It's a rather peculiar sounding term but it's there for a reason.&lt;/p&gt;
&lt;h3 id="rurki"&gt;Rurki&lt;/h3&gt;
&lt;p&gt;&lt;img alt="rurki" src="https://granitosaur.us/images/rurki.jpg"/&gt; &lt;/p&gt;
&lt;p&gt;There are few traditional Polish dishes, but as someone who's from eastern europe himself, I didn't find them unique or interesting at all. All except one: Rurki.&lt;br/&gt;
Rurki is a waffle roll with sweet cream inside of it. It's pretty simple but boy it's delicious! It's sweet, crunchy and creamy at the same time - a perfect dessert!&lt;br/&gt;
The best rurki we've eat were located in &lt;a href="https://www.tripadvisor.com/Attraction_Review-g274856-d2235946-Reviews-Zlote_Tarasy-Warsaw_Mazovia_Province_Central_Poland.html"&gt;&lt;strong&gt;Zloty Tarasy&lt;/strong&gt;&lt;/a&gt; shopping mall. At the very bottom floor there is a small sweets kiosk that offers the best rurki in the city!&lt;/p&gt;
&lt;h3 id="few-cool-food-joints"&gt;Few Cool Food Joints&lt;/h3&gt;
&lt;p&gt;There wasn't that much to do in Warsaw, at least during our trip, so we ate out a lot. Here's a short list of my favourite places, that I highly recommend!&lt;/p&gt;
&lt;h4 id="mango-vegan-street-food"&gt;&lt;a href="https://www.tripadvisor.com/Restaurant_Review-g274856-d8529497-Reviews-Mango_Vegan_Street_Food-Warsaw_Mazovia_Province_Central_Poland.html"&gt;Mango Vegan Street Food&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img alt="mango vegan street food" src="https://media-cdn.tripadvisor.com/media/photo-s/09/c0/d2/6d/mango-vegan-street-food.jpg"/&gt;&lt;br/&gt;
As I've mentioned above the place is great. It's quite cheap and the food is great. You can get daily deals (that last whole day) and some weekdays have discounts on some dishes. The food selection isn't great, mostly it's just flavours and variants of few dishes, but it's enough for few visits a week. A great place for a quick snack or a lengthier sit down! &lt;br/&gt;
Personal favourite and highly recommended: Pomegranate Hummus!  &lt;/p&gt;
&lt;h4 id="bubbleology"&gt;&lt;a href="https://www.tripadvisor.com/Restaurant_Review-g274856-d7189289-Reviews-Bubbleology-Warsaw_Mazovia_Province_Central_Poland.html"&gt;Bubbleology&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img alt="bubbleology" src="https://instagram.fhen1-1.fna.fbcdn.net/t51.2885-15/e35/14711881_1261966857193875_5168882141601202176_n.jpg"/&gt;&lt;br/&gt;
Bubble tea used to be a pretty huge fad across the whole Europe few years ago. However these days it's no longer even a thing in Estonia or Lithuania. Bubble tea is a tea drink with syrup-like bubbles that you can suck in using bigger than usual straw, it's a mix of a dessert and a drink which seems to be the strategy of the biggest cafe brand Starbucks, so we know it works!  &lt;br/&gt;
There's nothing particularity interesting about this shop other than it's themed as a science lab where bubble tea science happens. They really go all out by even dressing the clerks in lab coats and devising made up bubble tea formulas on the walls.  &lt;/p&gt;
&lt;p&gt;Regarding the prices - they are somewhat steeper than you'd expect even when Bubble tea in general is considered to be more expensive than it should. 
It wasn't the best bubble tea I had but it was good nevertheless so if you miss bubble tea or even worse - never tried it - check this place out!  &lt;/p&gt;
&lt;h4 id="mins-onigiri"&gt;&lt;a href="https://www.yelp.com/biz/mins-onigiri-warszawa?osq=onigiri"&gt;Min's Onigiri&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img alt="min's onigiri place" src="https://instagram.fhen1-1.fna.fbcdn.net/t51.2885-15/e35/14718290_983459391766489_1937566249529638912_n.jpg"/&gt;&lt;br/&gt;
&lt;em&gt;Onigiri&lt;/em&gt; is a Japanese rice ball that can be can contain some flavouring in the middle of it and topped of with dry seaweed wrapping. Historically it was invented as food for travelers, so it's a perfect food for tourists!&lt;br/&gt;
I really enjoyed this place, it only serves around 8 kinds of different rice balls and only 3 of them were vegetarian friendly, but all 3 of them were delicious. It also serves miso soup which is more or less bullion alternative Japanese use.&lt;br/&gt;
The onigiri themselves aren't that big, so you most likely want to take several. They are extremely delicious however and the miso + onigiri combo ended up being a perfect autumn food for tourists!  &lt;/p&gt;
&lt;p&gt;Regarding the prices - it's really reasonable. One rice ball + miso soup ended up being 10 PLN (2.3 euros) and 7 without the soup.  &lt;/p&gt;
&lt;h4 id="tel-aviv-food-wine"&gt;&lt;a href="https://www.tripadvisor.com/Restaurant_Review-g274856-d2619568-Reviews-Tel_Aviv_Food_Wine-Warsaw_Mazovia_Province_Central_Poland.html"&gt;Tel Aviv Food &amp;amp; Wine&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img alt="tel aviv restaurant during summer" src="https://s3-media4.fl.yelpcdn.com/bphoto/oGNq8N4egk1fMxpk672_TQ/o.jpg"/&gt; &lt;/p&gt;
&lt;p&gt;Another vegan place, this time though, it's a quite fancy one. The place itself is pretty close to the center and easy to reach, however it's pretty small so you should avoid going there during peak hours.&lt;br/&gt;
The food is very fancy, but personally I wasn't impressed - it was very pretty but didn't have taste or price value to match it. I think I would reserve this place for more fancy occasions rather than a lunch break or a meet up.&lt;/p&gt;
&lt;h4 id="green-caffe-nero"&gt;&lt;a href="http://www.greencaffenero.pl/en/"&gt;Green Caffe Nero&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;There really isn't much to say about this place. It's a rather regular caf&amp;eacute;, but it's really &lt;strong&gt;affordable&lt;/strong&gt;, has decent food, tea and coffee, and most importantly it's &lt;strong&gt;quite cozy&lt;/strong&gt; - so it's perfect for tourist stop for a tired, cold and worn-out tourist as myself!&lt;br/&gt;
Sitting down with a cup of hot tea, almost italian sandwich and writing some code or a blog for half an hour is always a pleasant experience, wherever you are.&lt;/p&gt;
&lt;p&gt;You can find them pretty much on every corner, sometimes you can even see another one from sitting inside one yourself :D&lt;/p&gt;
&lt;h3 id="few-cool-places_1"&gt;Few Cool Places&lt;/h3&gt;
&lt;p&gt;Before we left to Poland we did some research of what to visit but couldn't find much other than war museums that are present in every European city and honestly - they bore me to death. However once we got there we found few places that are definitely worth visiting and here they are!   &lt;/p&gt;
&lt;p&gt;&lt;em&gt;One place I don't mention that might be worth visiting is Copernicus Science Center. The reason why I don't think it's worth mentioning that it's always full of kids and unless you spend a long time in warsaw to find one day that isn't - it's not worth the headache.&lt;/em&gt;&lt;/p&gt;
&lt;h4 id="university-library"&gt;University Library&lt;/h4&gt;
&lt;p&gt;&lt;img alt="warsaw library" src="https://media-cdn.tripadvisor.com/media/photo-o/0d/07/2c/13/ogrod-zloty.jpg"/&gt;&lt;br/&gt;
Library? But we're on vacation! &lt;br/&gt;
Weirdly enough it was probably my favourite place in Warsaw. The main attraction was not the library itself, but what's on top of it! The roof of the library is a beautiful garden with all sorts of growth and shapes that create this unique and cosy atmosphere. &lt;br/&gt;
There were very few people and the weather wasn't that bad. It's near the river so it can get a bit windy, so pack up a scarf!&lt;/p&gt;
&lt;h4 id="technology-museum-and-palace-of-culture-and-science"&gt;Technology Museum and Palace of Culture and Science&lt;/h4&gt;
&lt;p&gt;&lt;img alt="Place of Culture and Science image" height="500" src="https://granitosaur.us/images/poland_palace.jpg"/&gt;&lt;br/&gt;
Palace of Culture and Science was gifted to Poland by Russia after WW2. It's a really beautiful building that since in the very center of Warsaw.&lt;br/&gt;
Aside of being pretty it also hosts a pretty cool Technology museum inside of it.It contains exhibits ranging from old motorcycles to nintendo games and it's really fun!&lt;br/&gt;
Few of the highlights: Space technology exhibit, video games, old vehicles (from bikes to planes)  &lt;/p&gt;
&lt;h2 id="pycon-pl_2"&gt;Pycon PL&lt;/h2&gt;
&lt;p&gt;&lt;img alt="pyconpl 2016" src="https://granitosaur.us/images/pyconpl.png"/&gt;&lt;/p&gt;
&lt;p&gt;We got there in the morning of Thursday, the first day of the conference. My girlfriend attended &lt;strong&gt;Pyladies&lt;/strong&gt; workshop while I checked us in. I don't think she enjoyed it as much as I thought she would, but it certainly piqued her interest in programming, so it wasn't bad!  &lt;/p&gt;
&lt;p&gt;The hotel is huge - it supports over 1600 guests and takes a bit under 5 minutes just to go from one end of it to the other. The cafeteria was great and served a sizeable selection of dishes for breakfast, lunch and dinner. As a vegetarian I only felt left out for one lunch where everything seemed to contain meat which means I ate a full plate of roasted potatoes - just like home!  &lt;/p&gt;
&lt;p&gt;The first few talks were half interesting and half boring. I feel like with many of the talks tend to be longer than they actually need to and some people tend to dig into details a bit too much - the talks in this pycon were not an exception.&lt;br/&gt;
I think a lot of the presenters miss the point that the talk you are giving should &lt;strong&gt;pique the interest of the listener or tell a story&lt;/strong&gt; rather than try sum up a framework or some technique in 10 slides.&lt;br/&gt;
For this reason, I enjoyed the lightning talks the most though that remains to be true for every conference I've been to.&lt;/p&gt;
&lt;p&gt;During the lunch time I met a Lithuanian which was really surprising. Not only python isn't huge in Lithuania but when I was purchasing the tickets one of the hosts mentioned that I'd be the first and only person from Lithuania to attend PyconPL. I'm glad that it turned out not being the case because Lithuania really needs more Python! (or anything that is not php for that matter :D)&lt;/p&gt;
&lt;h3 id="meeting-scrapinghubbers"&gt;Meeting Scrapinghubbers&lt;/h3&gt;
&lt;p&gt;This year scrapinghub wasn't sponsoring the conference thus we didn't have a booth. However there were 3 other scrapinghubbers present in the conference other than myself. We chatted and played board games together and I can firmly say that in the two years that I've spent working in Scrapinghub I haven't met a single dislikeable person there and this conference solidified this experience even more!  &lt;/p&gt;
&lt;p&gt;Shoutout to Pawel and two Michals from scrapinghub who made this conference even more fun than it could have been!&lt;/p&gt;
&lt;h3 id="board-and-retro-games"&gt;Board and Retro Games&lt;/h3&gt;
&lt;p&gt;Every evening board game event has been held in one of the conference rooms and that's probably where the majority of the conference was spent. There was a board game rental company that provided the conference with some board games as well as some people bringing their own. We brought Exploding Kittens, Loot Letter and Coup. Exploding Kittens by far received the most attention, though the other two games weren't far behind too.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="polish Mysterium" src="https://granitosaur.us/images/polish_mysterium.jpg"/&gt;&lt;br/&gt;
We didn't get a chance to play any board games that we haven't touched previously with the exception of &lt;strong&gt;Mysterium&lt;/strong&gt;! Or at least Polish version of it, which is the original version of the game and by some considered to be superior. It's a brilliant coop game where one player is a ghost who was hanged for a murder he did not commit and has gives hints to the other players of who was the real murderer! Some people describe it as reverse Dix It which in a way it is, but in my opinion, much more fun and engaging.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Donkey Konga for gamecube" src="http://media.ign.com/games/image/object/572/572741/Donkey-Konga-1_Game-and-Bongos_Cube_US_ESRB.jpg"/&gt;&lt;br/&gt;
Another cool piece of entertainment was retro computer games, a full room of them! They had various consoles and computers ranging from atari to windows 95 Worms Armageddon machines. There were open and running through the whole day and you could play something in-between the talks or after having lunch. My personal favorite was Donkey Konga - a party, rhythm game for GameCube. It's like guitar hero but instead of a guitars, players use a set of bongo drums which create a hilarious and often silly atmosphere!&lt;/p&gt;
&lt;h1 id="summary_2"&gt;Summary&lt;/h1&gt;
&lt;p&gt;Initially we didn't find much to do when we did research about Warsaw, however once we got there we got pleasantly surprised by the city and what it offered!  The city is big and beautiful. The center is structured in nice blocks that are easy to traverse and navigate. Every block being full of various small shops, caf&amp;eacute;s or restaurants. And even though there were indeed only few experience places we enjoyed them greatly!&lt;br/&gt;
Pycon was a blast too! The educational opportunities weren't that great (the talks, workshops etc.) but the board games and leisure activities were more than enough to compensate!  &lt;/p&gt;
&lt;p&gt;Will definitely consider going next year! &lt;br/&gt;
Only regret was not taking more luggage.&lt;/p&gt;</content><category term="self"></category><category term="pycon"></category><category term="python"></category><category term="poland"></category><category term="travel"></category><category term="warsaw"></category></entry><entry><title>How to parse complicated json trees.</title><link href="https://granitosaur.us/crawling-json.html" rel="alternate"></link><published>2016-10-10T00:00:00+02:00</published><updated>2016-10-10T00:00:00+02:00</updated><author><name>Bernardas Ališauskas</name></author><id>tag:granitosaur.us,2016-10-10:/crawling-json.html</id><summary type="html">&lt;p&gt;Often when web-crawling you can find access to website's api which provides direct JSON of a product, however it's not always so easy to find what you need in what could be a multi-layer mess of a json.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Often when web-crawling you can find access to website's api which provides direct JSON of a product, however it's not always so easy to find what you need in what could be a multi-layer mess of a json.&lt;/p&gt;
&lt;p&gt;In this blog-post I'll cover few tools and ways to deal with really ugly json trees that you probably don't want to iterate through manually using dictionary key indices.&lt;br/&gt;
&lt;strong&gt;If you don't care about the research you can just skip to the &lt;a href="#right"&gt;right tool&lt;/a&gt; and &lt;a href="#solving"&gt;solving of the real life case&lt;/a&gt; sections at the end&lt;/strong&gt;.&lt;/p&gt;
&lt;h1 id="cause"&gt;Cause&lt;/h1&gt;
&lt;p&gt;Often websites, especially the ones that sell various products tend to overcomplicate their apis by stacking everything in one huge json tree that is at least 10 layers deep and is impossible to understand for an outsider or maybe even other developers in the company.&lt;/p&gt;
&lt;p&gt;In this case we'll take a look at small examples of &lt;a href="http://ah.nl"&gt;http://ah.nl&lt;/a&gt; responses and how can we deal with them without spending hours trying to reverse engineer the whole process.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example info&lt;/strong&gt;:&lt;br/&gt;
Product url: &lt;a href="http://www.ah.nl/producten/product/wi166580/maggi-opkikker-rundvlees"&gt;http://www.ah.nl/producten/product/wi166580/maggi-opkikker-rundvlees&lt;/a&gt;&lt;br/&gt;
Product api response: &lt;a href="https://ptpb.pw/aZ_S"&gt;https://ptpb.pw/aZ_S&lt;/a&gt;&lt;br/&gt;
If you put this response through some json visual tool like &lt;a href="http://jsonviewer.stack.hu/"&gt;http://jsonviewer.stack.hu/&lt;/a&gt; you'll notice what a huge mess it is: &lt;/p&gt;
&lt;p&gt;&lt;img alt="example json view" src="https://granitosaur.us/images/json-crawling.png"/&gt;&lt;/p&gt;
&lt;p&gt;Multiple layers, multiple elements, list in a dict in a list in a dict and to parse that you'd end up doing something like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'_embedded'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'lanes'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'_embedded'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'items'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And that's just half-way through the tree. For example to find the sku you'd have to use something like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sku = data['_embedded']['lanes'][4]['_embedded']['items'][0]['_embedded']['product']['id']
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now that's with hard-coding of list indices which are very likely to change for every product, so on top of that ugly line above you'd have to use multiple list comprehensions to find the correct list item from the &lt;code&gt;lanes&lt;/code&gt; or &lt;code&gt;items&lt;/code&gt; lists.  This is bad, ugly, unreliable and extremely painful to work with.&lt;/p&gt;
&lt;h1 id="tools-to-solve-this"&gt;Tools to Solve This&lt;/h1&gt;
&lt;p&gt;There are several ways this can be approaches and let me spoil it for you, majority of them are bad, so we'll start off with those.&lt;/p&gt;
&lt;p&gt;To demonstrate these tools better we'll be parsing this simple json:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;data = """{
    "one": {
        "two": [{
            "four": {
                "name": "four1_name"
            }
        }, {
            "four": {
                "name": "four2_name"
            }
        }]
    }
}"""
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;All examples below are also available on iPython notebook if you want to mess around with them yourself &lt;a href="https://granitosaur.us/data/crawling-json_examples.ipynb"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="wrong-flattening-the-json"&gt;Wrong: Flattening The Json&lt;/h2&gt;
&lt;p&gt;At first glance this might appear as an obvious solution - just flatten everything to the first level! However this brings out a huge issue with keys. Because every key has to be unique, when flattening the dictionary you need to merge several keys into one to preserve the tree order.
If we were to flatten our &lt;code&gt;data&lt;/code&gt;, it would end up looking like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;data = """{
    "one_two_four1_name": "four1_name",
    "one_two_four2_name": "four2_name",
    }"""
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In a way you might think it looks nice, but the truth is that it's really unpredictable and hard to parse in a more complex context, since you can only select individual values. This might be useful for some edge cases where you only need 1 field the json tree is only two or tree levels deep, but otherwise it's not worth bothering with.&lt;/p&gt;
&lt;h2 id="wrong-jmespath-jsonpath-and-jsoniq-etc"&gt;Wrong: Jmespath, JSONPath and JSONiq etc.&lt;/h2&gt;
&lt;p&gt;These few libraries in a way designed specifically to solve this issue. It seems that json is notoriously bad when it comes to this issue, so tools like theses are dime a dozen on github and while they are great, they fall short when in comes to web-crawling or similar use cases.   &lt;/p&gt;
&lt;p&gt;However there are two major issues with these tools:  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;First one being that some of them like the very &lt;code&gt;Jmespath&lt;/code&gt;'s &lt;strong&gt; expressions root-bound&lt;/strong&gt; which means non-rooted expressions like xpath's &lt;code&gt;//product/name&lt;/code&gt; are not possible. This means that you need to write this ugly chain which is barely different to our dict key indices one:&lt;/p&gt;
&lt;p&gt;root.foo.bar[].foo2.bar2.product.mynode&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The only improvement here is that we can do a bit of recursion by calling &lt;code&gt;[]&lt;/code&gt; for every list element, saving us a few list comprehension calls. And it definitely looks nicer, doesn't it?&lt;br/&gt;
It is still bad though since at any point the tree might change and our crawler will break because we are root bound.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The second issue being is that all of them &lt;strong&gt;are extremely bloated&lt;/strong&gt;, to the point where they not only design their own parsing logic but also design their own syntax.   &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When you are crawling a website you already have your own parsing tools to parse the html/xml (like &lt;code&gt;lxml&lt;/code&gt; or &lt;code&gt;parsel&lt;/code&gt;) and anything other would just introduce obvious redundancy and unnecessary complexity. &lt;/p&gt;
&lt;h2 id="almost-right-js2xml"&gt;Almost Right: js2xml&lt;/h2&gt;
&lt;p&gt;First I'd like to start off with and give a shout out to a great tool called &lt;code&gt;js2xml&lt;/code&gt; which maintained by Scrapinghub. It pretty much does what it says - converts javascript code to an xml tree and it's &lt;em&gt;almost&lt;/em&gt; the right tool for our issue, almost.&lt;br/&gt;
Since json is part of javascript, this means we can use this tool to parse it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;lxml&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;etree&lt;/span&gt;
&lt;span class="c1"&gt;# we need to wrap our data json in variable declaration&lt;/span&gt;
&lt;span class="c1"&gt;# for js2xml to interpret it&lt;/span&gt;
&lt;span class="n"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;js2xml&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'var foo = '&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;etree&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tostring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pretty_print&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is the result:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;program&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;var&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"foo"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;object&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;property&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"one"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;object&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;property&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"two"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;array&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;object&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;property&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"four"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;object&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;property&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                      &lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;four1_name&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;/object&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;/object&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;object&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;property&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"four"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;object&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;property&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                      &lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;four2_name&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;/object&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;/object&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/array&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/object&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/object&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/var&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/program&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As you can see it works and could probably be parsed with xpath. It's really ugly and if we were to write an xpath for it, it would be unnecessary complicated and long, but it would work! &lt;br/&gt;
If you are already using it to parse javascript somewhere you might just go with it to reduce dependencies if you wish so.&lt;/p&gt;
&lt;h1 id="right_1"&gt;Right: Converting json to xml and Parsing It With xpath&lt;/h1&gt;
&lt;p&gt;I found two tools and either one of them combined with either &lt;a href="http://lxml.de/"&gt;&lt;code&gt;lxml&lt;/code&gt;&lt;/a&gt; or &lt;a href="https://github.com/scrapy/parsel"&gt;&lt;code&gt;parsel&lt;/code&gt;&lt;/a&gt; selectors create this beautiful, perfect json-crawling combo for your crawler! &lt;/p&gt;
&lt;p&gt;For unaware &lt;code&gt;lxml&lt;/code&gt; is a really great tool for parsing xml and html while &lt;code&gt;parsel&lt;/code&gt; is built on top of it to make it even greater, so I highly recommend checking it out!
Fun fact - it's also used by &lt;a href="https://github.com/scrapy/scrapy"&gt;scrapy&lt;/a&gt; and that's where it originated.&lt;/p&gt;
&lt;p&gt;Getting back to the point, the two tools that are pretty much alternative to each other are &lt;a href="https://github.com/quandyfactory/dicttoxml"&gt;&lt;code&gt;dicttoxml&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/delfick/python-dict2xml"&gt;&lt;code&gt;dict2xml&lt;/code&gt;&lt;/a&gt;. They are essentially the same thing but I thought I'd mention both since I'm not sure which one is better and requires the recognition. &lt;br/&gt;
For sake of being brief I'll show off &lt;code&gt;dicttoxml&lt;/code&gt; + &lt;code&gt;parsel&lt;/code&gt; only:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;dicttoxml&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dicttoxml&lt;/span&gt;
&lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dicttoxml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;attr_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# the tree we get:&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;one&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;two&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;four&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;four1_name&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;four&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;four&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;four2_name&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;four&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;two&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;one&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now we can parse this tree using &lt;code&gt;parsel.Selector&lt;/code&gt; and xpath:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;parsel&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Selector&lt;/span&gt;
&lt;span class="n"&gt;sel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Selector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'utf-8'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;# and get the names with&lt;/span&gt;
&lt;span class="n"&gt;sel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xpath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"//name/text()"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# ['four1_name', 'four2_name']&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Pretty mind blowing how we solved this mess with one 300 loc big package from pypi and one short xpath.&lt;/p&gt;
&lt;h1 id="solving"&gt;Solving Our Example&lt;/h1&gt;
&lt;p&gt;Now that we have chosen a tool let's see how well it works on a real life example we got ourselves at the beginning of this blog: &lt;a href="http://www.ah.nl/producten/product/wi166580/maggi-opkikker-rundvlees"&gt;http://www.ah.nl/producten/product/wi166580/maggi-opkikker-rundvlees&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;I'm going to spoil you the joy of reverse engineering the products api and tell you the api url in this case is: 
&lt;code&gt;'http://www.ah.nl/service/rest/delegate?url=/producten/product/wi166580/x'&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Lets assume we already have the page source in &lt;code&gt;body&lt;/code&gt; variable and dive in:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;dicttoxml&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dicttoxml&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;parsel&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Selector&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;

&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;sel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Selector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dicttoxml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attr_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;# now we can find things very easily!&lt;/span&gt;
&lt;span class="c1"&gt;# sku:&lt;/span&gt;
&lt;span class="n"&gt;sel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xpath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"//product/id/text()"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# [u'wi166580']&lt;/span&gt;
&lt;span class="c1"&gt;# price:&lt;/span&gt;
&lt;span class="n"&gt;sel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xpath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"//product//pricelabel/now/text()"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# [u'0.82']&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1 id="conclusion"&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Mission accomplished! We managed to parse multi-layer monster with very few, simple xpaths and a small package from pipy!&lt;br/&gt;
Personally I wish I started doing this earlier because iterating through monsters like this one key at the time is extremely tedious and it breaks every time the website decides to update something. &lt;br/&gt;
Hopefully this write up can save someone few hours and an early balding. :D&lt;/p&gt;</content><category term="code"></category><category term="python"></category><category term="web-crawling"></category><category term="scrapy"></category></entry><entry><title>Going to Pycon Poland!</title><link href="https://granitosaur.us/pycon-pl.html" rel="alternate"></link><published>2016-10-10T00:00:00+02:00</published><updated>2016-10-10T00:00:00+02:00</updated><author><name>Bernardas Ališauskas</name></author><id>tag:granitosaur.us,2016-10-10:/pycon-pl.html</id><summary type="html">&lt;p&gt;Going to Pycon PL and checking out Warsaw!&lt;/p&gt;</summary><content type="html">&lt;p&gt;Last year in Euro-Python 2015 someone from PyconPL had a lightning talk about PyconPL and since that moment I was sold. However I missed it by few days. This year however, I'm going and I'm taking a short vacation to explore Warsaw too!&lt;/p&gt;
&lt;p&gt;There's a bunch of info about the conference on the &lt;a href="https://pl.pycon.org/2016/about_en.html"&gt;official page&lt;/a&gt; and I don't want to be redundant, but the venue looks awesome and in overall the event looks pretty huge. Primarily I just wanted to share how much money do you need to attend something like this, how do you get there and lastly whether it is worth it.   &lt;/p&gt;
&lt;h1 id="the-route"&gt;The route&lt;/h1&gt;
&lt;p&gt;&lt;img alt="Trip route from Tartu to Warsaw" src="https://granitosaur.us/images/pycon-pl-travel.png"/&gt; &lt;/p&gt;
&lt;p&gt;So we're taking a bus from Tartu -&amp;gt; Riga which is really the most tedious part of this trip. As you can see in the image the trip almost takes &lt;strong&gt;4 hours&lt;/strong&gt; and the bus leaves early and often is packed full. We'll spend two hours in Riga's airport waiting for our flight, which I really don't mind since Riga's airport is really nice. And to finish it off we'll take a &lt;strong&gt;1.25&lt;/strong&gt; hour long flight to Warsaw itself. I love flying, more accurately I like take offs and landings so having such short flights, where majority of the time will be the take off and the landing, sounds great!&lt;br/&gt;
In conclusion, we leave at 7:00 and we should be in Warsaw at 14:35 (warsaw's time). So this ends up being &lt;strong&gt;8.5 hours for 900km&lt;/strong&gt; which really doesn't have a good hour/km ratio per se but considering the down-times and the location it really isn't bad at all! &lt;br/&gt;
Lastly the conference organizes a bus that will take us from Warsaw to Ossa village, which is few kilometers away from Warsaw and there the conference itself will be held. &lt;/p&gt;
&lt;h1 id="the-cost"&gt;The Cost&lt;/h1&gt;
&lt;h3 id="conference"&gt;Conference&lt;/h3&gt;
&lt;p&gt;Since I was a bit late to register and missed the early bird prices, I had to drop 211&amp;euro; per person (422&amp;euro; for two people) for 4 days of the conference, including food and accommodation. This might seem like a lot but compared to other conferences is really little. &lt;br/&gt;
I actually chatted with one of the hosts a bit and he mentioned that pretty much the whole fee goes to the hotel that is hosting the conference, after visiting the website I can understand that since it advertises the rooms at 90&amp;euro; a night the conference fee seems to be very reasonable indeed!&lt;/p&gt;
&lt;h3 id="travel"&gt;Travel&lt;/h3&gt;
&lt;p&gt;The plane tickets from Riga to Warsaw and back ended up being 62&amp;euro; per person(125&amp;euro; for two) which is slightly above from the best I could find. At one point I got 42&amp;euro; deals pop up but the payment didn't go through and the next day it popped to 62&amp;euro;. I know airline websites are really fishy when it comes to pricing, storing profiles and cookies to jack up the price whenever they see fit but I'm certain this was just an unfortunate coincidence.&lt;br/&gt;
The bus to Riga from Tartu ended up being 30&amp;euro; per person (60&amp;euro; in total) both ways.&lt;/p&gt;
&lt;p&gt;So &lt;strong&gt;total ended up being 303&amp;euro; per person&lt;/strong&gt; which is pretty cheap for a conference 906km away!&lt;br/&gt;
This doesn't include any traveling expenses which I'll be sure to calculated and include in aftermath blogpost!&lt;/p&gt;
&lt;p&gt;The last python conference I went to was EuroPython 2015 and it was super fun, mostly because I got to meet a bunch of coworkers from Scrapinghub, I hope PyconPL can live up to the hype and I'll be sure to post about it either way.&lt;/p&gt;</content><category term="self"></category><category term="pycon"></category><category term="python"></category><category term="poland"></category><category term="travel"></category></entry><entry><title>First post. Hello Pelican!</title><link href="https://granitosaur.us/installing-pelican.html" rel="alternate"></link><published>2016-10-09T00:00:00+02:00</published><updated>2016-10-09T00:00:00+02:00</updated><author><name>Bernardas Ališauskas</name></author><id>tag:granitosaur.us,2016-10-09:/installing-pelican.html</id><summary type="html">&lt;p&gt;Starting up the blog with Python and Pelican static blog generator!&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've decided to start a blog after Python package called Pelican caught my eye.&lt;br/&gt;
Pelican is a tool to generate a static blog from reStructuredText or Markdown input files. And most importantly it looks to be really fun, full python with jinja2 templating, which means it's fully extendable, configurable and modifiable as it's under GPL license.&lt;/p&gt;
&lt;h3 id="installing-pelican"&gt;Installing Pelican&lt;/h3&gt;
&lt;p&gt;&lt;img alt="Pelicans are cool" src="https://granitosaur.us/images/pelican-bird.jpg"/&gt;&lt;br/&gt;
The setup for &lt;code&gt;Pelican&lt;/code&gt; is pretty straightforward just run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;~&amp;gt; pip install pelican  &lt;span class="c1"&gt;# Installing Pelican package for python&lt;/span&gt;
~&amp;gt; mkdir blog &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; blog  &lt;span class="c1"&gt;# Create and jump into your blog directory!&lt;/span&gt;
~/blog/&amp;gt; pelican-quickstart
    ... &lt;span class="c1"&gt;#answer some simple questions here&lt;/span&gt;
~/blog/&amp;gt; vim content/first-page.md
    ... &lt;span class="c1"&gt;#write your blog here in simple markdown&lt;/span&gt;
~/blog/&amp;gt; pelican content  &lt;span class="c1"&gt;# regenerate website&lt;/span&gt;
~/blog/&amp;gt; &lt;span class="nb"&gt;cd&lt;/span&gt; output
~/blog/output&amp;gt; python -m pelican.server  &lt;span class="c1"&gt;# run pelican server to test locally&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now connect to &lt;code&gt;http://localhost:8000&lt;/code&gt; and there you go!&lt;br/&gt;
You can check &lt;a href="http://docs.getpelican.com/en/latest/content.html#articles-and-pages"&gt;here&lt;/a&gt; for how to template your message how to format your blog entry.&lt;/p&gt;
&lt;h3 id="vim-markdown-highlight-for-md-files"&gt;Vim markdown highlight for .md files&lt;/h3&gt;
&lt;p&gt;While going through the installation I've noticed that markdown doesn't have highlighting in vim which was peculiar. I found &lt;a href="http://superuser.com/questions/701496/no-syntax-highlight-on-md-files"&gt;this post which describes a simple fix&lt;/a&gt;.&lt;br/&gt;
Simply create directories and file: &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;~/.vim/ftdetect/markdown.vim
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;with content: &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;au BufNewFile,BufRead *.md  setf markdown
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="theming-pelican"&gt;Theming Pelican&lt;/h3&gt;
&lt;p&gt;The default Pelican theme is pretty great however I stumbled on &lt;a href="https://github.com/alexandrevicenzi/Flex"&gt;flex-theme&lt;/a&gt; on &lt;a href="https://github.com/getpelican/pelican-themes"&gt;pelican theme repo&lt;/a&gt; on github. So that's my choice for now, but I'd like to touch up the color scheme a bit. Check out &lt;a href="http://docs.getpelican.com/en/stable/pelican-themes.html"&gt;&lt;code&gt;pelican-themes&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="configuring-pelican"&gt;Configuring Pelican&lt;/h3&gt;
&lt;p&gt;A lot of bells and whistles come straight out of the box with the pelican and your theme. For example to setup Disqus commnets all I had to do is add &lt;code&gt;DISQUS_SITENAME = "granitosaurus"&lt;/code&gt; where &lt;code&gt;granitosaurus&lt;/code&gt; is my registered name of my disqus account.&lt;/p&gt;
&lt;h3 id="publishing-pelican"&gt;Publishing Pelican&lt;/h3&gt;
&lt;p&gt;Since Pelican generates a static webpage you can use anything to publish it. I decided to use &lt;a href="http://docs.getpelican.com/en/stable/tips.html#user-pages"&gt;github user pages&lt;/a&gt; which is a bit more complicated than the docs make it out to be. For user pages I like to keep the whole source code in branch &lt;code&gt;source&lt;/code&gt; and keep the generated output in &lt;code&gt;master&lt;/code&gt; as per github's user pages rule. Then use &lt;code&gt;ghp-import&lt;/code&gt; to automatically update master code with the most recent  &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;git&lt;/span&gt; &lt;span class="n"&gt;checkout&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;
&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;pelican&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="n"&gt;publishconf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;
&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;ghp&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;output&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="n"&gt;master&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The above will make source branch, generate blog and push the output to &lt;code&gt;master&lt;/code&gt; so it's viewable at https://username.github.io &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt; The most important bit is to set &lt;code&gt;SITEURL&lt;/code&gt; in your &lt;code&gt;publishconf.py&lt;/code&gt; to &lt;code&gt;https://username.github.io&lt;/code&gt; make sure it's &lt;strong&gt;HTTPS&lt;/strong&gt; since default SITEURL generated by pelican is http and github pages requires https. This took me an hour of messing around to finally figure out.&lt;/p&gt;
&lt;h3 id="wrap-up"&gt;Wrap Up&lt;/h3&gt;
&lt;p&gt;So far Pelican took quite a bit of work to get things going. It looks quite simple but there's a bunch of little quirks that are really hard to debug. It's not as easy as starting up a wordpress blog but it's quite fun and it seems to be really flexible. &lt;br/&gt;
Let's see if it pays off! &lt;/p&gt;
&lt;p&gt;Checkout the source for more at https://github.com/Granitas/granitas.github.io/tree/source&lt;/p&gt;</content><category term="code"></category><category term="pelican"></category><category term="python"></category><category term="blog"></category></entry></feed>