From 03cacc61206f3c77f6ad36a783158ff3ceced7cd Mon Sep 17 00:00:00 2001 From: Wesley Moore Date: Mon, 4 Jul 2022 14:07:33 +1000 Subject: [PATCH] Add Generating RSS Feeds From Web Pages With RSS Please --- .../2022/generate-rss-from-webpage/index.md | 188 ++++++++++++++++++ .../rsspls-output.png | Bin 0 -> 21166 bytes 2 files changed, 188 insertions(+) create mode 100644 v2/content/posts/2022/generate-rss-from-webpage/index.md create mode 100644 v2/content/posts/2022/generate-rss-from-webpage/rsspls-output.png diff --git a/v2/content/posts/2022/generate-rss-from-webpage/index.md b/v2/content/posts/2022/generate-rss-from-webpage/index.md new file mode 100644 index 0000000..bf5a69e --- /dev/null +++ b/v2/content/posts/2022/generate-rss-from-webpage/index.md @@ -0,0 +1,188 @@ ++++ +title = "Generating RSS Feeds From Web Pages With RSS Please" +date = 2022-07-04T09:54:29+10:00 + +[extra] +#updated = 2022-01-27T21:07:32+10:00 ++++ + +Sometimes I come across a web page that I'd like to revisit when there's +new content. Typically, I do this by subscribing to the [RSS feed][feed] in +[Feedbin]. Unfortunately some sites don't provide an RSS feed, which is why I +built [RSS Please][rsspls] (`rsspls`). RSS Please allows you to generate an RSS +feed by extracting specific parts of a web page. In this post I give a bit of +background on the tool and how I'm running it in my Docker infrastructure. + + + +### Background + +Sometimes an RSS feed isn't available on a website. If the site is open source +I will often try to [open a PR to add or enable one][rss-pr]. That's not always +possible though. Other time the page may be one that would naturally think to provide a feed for, but one would still be useful. + +As an example, when we were looking to buy a house I noticed that listings +would often go live on agent's websites several days or more before they were +published to the big aggregators. The market was very competitive so I was +regularly visiting all the real estate agent websites to run my search, and +check for new listings. At the time I used [Feedfry] to create RSS feeds from +the search results. I could then subscribe to them in [Feedbin]. Paired with +the [Feedbin Notifier app][notifier] I received a notification on my phone +whenever there was a new listing matching my search criteria from any of the +agents. + +Feedfry is free with ads or paid subscription. I paid while house shopping but +let that lapse afterwards. I don't begrudge them funding the service with ads or +subscriptions but I figured I could probably put something together and +self-host it. At the same time providing a bit more control over how the +elements of the page were extracted to generate the feed. [RSS Please][rsspls] +is the result. + +RSS Please is an open-source command line application implemented in Rust. It +has no runtime dependencies and runs on UNIX-like platforms including FreeBSD, +Linux, and macOS. Once I resolve [this issue][windows-issue] it will run on +Windows too. The following sections describe how it's configured and how I'm +running it on my server. + +### Configuration + +The `rsspls` configuration file allows a number of feeds to be defined. It +uses [CSS Selectors][css] to describe how parts of each page will be extracted +to produce a feed. As an example here's a configuration that builds a feed +from this site—although I already have an RSS feed at + if you want to subscribe. + +```toml +# The configuration must start with the [rsspls] section +[rsspls] +output = "/tmp" + +[[feed]] +# The title of the channel in the feed +title = "Example WezM.net Feed" +# The output filename within the output directory to write this feed to. +filename = "wezm.rss" + +[feed.config] +url = "https://www.wezm.net/" +item = "article" +heading = "h3 a" +summary = ".post-body" +date = "time" +``` + +The configuration format is [TOML]. The `item` key selects `article` elements +from the page. `heading`, `summary`, and `date` are selectors upon the element +selected by `item`. `summary` and `date` are optional. `heading` is expected to +select an element with a `href` attribute, which is used as the link for the +item in the feed. + +### Running It + +Once installed running `rsspls` will update the configured feeds. Caching is +used to skip updates when the origin server indicates nothing has changed since +last time. By default `rsspls` looks for its configuration file in +`$XDG_CONFIG_HOME/rsspls/feeds.toml`, defaulting to +`~/.config/rsspls/feeds.toml` if `XDG_CONFIG_HOME` is not set. Alternatively +the path can be supplied with `--config`. + +{{ figure(image="posts/2022/generate-rss-from-webpage/rsspls-output.png", link="posts/2022/generate-rss-from-webpage/rsspls-output.png", alt="Screenshot of the output when running rsspls. It has several log messages prefixed with INFO describing the actions taken", caption="rsspls prints informational messages when updating feeds", width=335, border=false) }} + +### Deployment + +Since I +[host my things with Docker + Compose](@/posts/2022/alpine-linux-docker-infrastructure-three-years/index.md) +I'm running `rsspls` with Docker as well, but that's not required. There are +plenty of other ways you could go about it. E.g. you could have `cron` run +`rsspls` on your computer and `rsync` the feeds to a server. Some RSS aggregators +like [Liferea] even let you subscribe to local files. + +I create a Docker image from the `rsspls` binaries I publish: + +```dockerfile +FROM wezm-alpine:3.16.0 + +# UID needs to match owner of /home/rsspls/feeds volume +ARG PUID=1000 +ARG PGID=1000 +ARG USER=rsspls + +RUN addgroup -g ${PGID} ${USER} && \ + adduser -D -u ${PUID} -G ${USER} -h /home/${USER} -D ${USER} + +ARG RSSPLS_VERSION=0.2.0 + +RUN cd /usr/local/bin && \ + wget -O - https://releases.wezm.net/rsspls/${RSSPLS_VERSION}/rsspls-${RSSPLS_VERSION}-x86_64-unknown-linux-musl.tar.gz | tar zxf - && \ + mkdir /home/${USER}/feeds && \ + chown ${USER}:${USER} /home/${USER}/feeds + +COPY ./entrypoint.sh /home/${USER}/entrypoint.sh + +WORKDIR /home/${USER} + +USER ${USER} + +VOLUME ["/home/rsspls/feeds"] +ENTRYPOINT ["./entrypoint.sh"] +``` + +It uses my standard [Alpine] base image which is built from the "Mini root +filesystem" they publish and does not require any other packages to be +installed. + +I use an entry point script to run `rsspls` every 12 hours: + +```sh +#!/bin/sh + +set -e + +trap 'exit' TERM INT + +while true; do + rsspls --config /etc/rsspls.toml + sleep 1036800 # 12 hours +done +``` + +In my `docker-compose.yml` I have the following: + +```yaml + rsspls: + image: example.com/rsspls + volumes: + - ./rsspls/rsspls.toml:/etc/rsspls.toml:ro + - ./volumes/www/rsspls.wezm.net:/home/rsspls/feeds + restart: unless-stopped +``` + +The `./volumes/www/rsspls.wezm.net` path is shared with the container running `nginx`, so +the generated feeds are accessible at `rsspls.wezm.net`—although I'm not making them +obvious to visitors (there's no directory index so visiting that domain will just give +a 403 Forbidden error). + +### Conclusion + +This was a fun project to put together over a weekend. I get a lot of +satisfaction from building and self-hosting tools to solve my own problems. Not +everyone has the time or desire to do that though so if you're looking for +similar functionality check out [Feed43] and [Feedfry]. + +As mentioned the tool is open-source (MIT or Apache 2.0). Check out the repo at + and if you like what you see maybe give it a +star. + + +[Alpine]: https://alpinelinux.org/ +[css]: https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Selectors +[Feed43]: https://feed43.com/ +[feed]: https://en.wikipedia.org/wiki/RSS +[Feedbin]: https://feedbin.com/ +[Feedfry]: https://feedfry.com/ +[notifier]: https://feedbin.com/notifier +[rss-pr]: https://github.com/pulls?q=is%3Apr+author%3Awezm++rss+is%3Aclosed+ +[rsspls]: https://github.com/wezm/rsspls +[windows-issue]: https://github.com/wezm/rsspls/issues/4 +[TOML]: https://toml.io/ +[Liferea]: http://lzone.de/liferea/ diff --git a/v2/content/posts/2022/generate-rss-from-webpage/rsspls-output.png b/v2/content/posts/2022/generate-rss-from-webpage/rsspls-output.png new file mode 100644 index 0000000000000000000000000000000000000000..aa22384f2066ad0fe840c0159bba5247aec7aec5 GIT binary patch literal 21166 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 YX2c(laeY_e4`ZZUkCUD29Z#qJ6L8(6r2qf` literal 0 HcmV?d00001