{"id":20611862,"url":"https://github.com/qjake/pricekeeper","last_synced_at":"2026-04-17T23:06:43.975Z","repository":{"id":72513188,"uuid":"571076506","full_name":"qJake/pricekeeper","owner":"qJake","description":"A Private, Cloud-Enabled Price Tracker 💸","archived":false,"fork":false,"pushed_at":"2023-03-11T01:35:40.000Z","size":90,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-01-17T03:33:31.567Z","etag":null,"topics":["azure-storage","azure-storage-table","price-data","price-tracker","price-tracking-system"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"unlicense","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/qJake.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-11-27T04:26:36.000Z","updated_at":"2023-01-06T04:19:59.000Z","dependencies_parsed_at":"2023-03-17T23:00:29.648Z","dependency_job_id":null,"html_url":"https://github.com/qJake/pricekeeper","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qJake%2Fpricekeeper","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qJake%2Fpricekeeper/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qJake%2Fpricekeeper/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qJake%2Fpricekeeper/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/qJake","download_url":"https://codeload.github.com/qJake/pricekeeper/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":242259946,"owners_count":20098429,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["azure-storage","azure-storage-table","price-data","price-tracker","price-tracking-system"],"created_at":"2024-11-16T10:22:25.111Z","updated_at":"2026-04-17T23:06:38.953Z","avatar_url":"https://github.com/qJake.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 💸 PriceKeeper\r\n\r\nA private, automated, cloud-enabled price tracker.\r\n\r\n## Create Your Config File\r\n\r\nMake a file called `config.yaml` and store it somewhere safe, like `~/pricekeeper/config.yaml`.\r\n\r\nUse this as a jumping off point:\r\n\r\n```yaml\r\nstorage:\r\n  type: azure\r\n  account: mystorageaccountname\r\n  key: abcde............................67890==\r\n\r\nscheduler:\r\n  spreadtime: 30\r\n\r\nrules:\r\n  - name: Complete Guide to Docker for Beginners\r\n    category: Amazon\r\n    selector: '.a-price .a-offscreen'\r\n    url: https://www.amazon.com/dp/B08BW5Y73D/\r\n    hours: '*'\r\n\r\n  - name: Learning Python 5th Edition\r\n    category: Amazon\r\n    selector: '.a-price .a-offscreen'\r\n    template: amazon\r\n    url: https://www.amazon.com/dp/1449355730/\r\n    hours: 0,3,6,9,12,15,18,21\r\n\r\n  - name: Pixel 7 Pro\r\n    category: Google\r\n    regex:\r\n      - meta itemprop=\"lowPrice\" content=\"(.+?)\"\r\n      - meta itemprop=\"price\" content=\"(.+?)\"\r\n    url: https://store.google.com/product/pixel_7_pro\r\n    hours: '0,12'\r\n\r\n  - name: Pixel 7 Pro Case\r\n    category: Google\r\n    regex:\r\n      - meta itemprop=\"lowPrice\" content=\"(.+?)\"\r\n      - meta itemprop=\"price\" content=\"(.+?)\"\r\n    url: https://store.google.com/product/casemate_tough_clear_cases\r\n    hours: '0,12'\r\n```\r\n\r\nFor a complete list of configuration options, see below.\r\n\r\n## Run with Docker\r\n\r\n| Item             | Description                                     |\r\n| ---------------- | ----------------------------------------------- |\r\n| Image Name       | `qjake/pricekeeper:latest`                      |\r\n| Port(s)          | `9600`                                          |\r\n| Mount(s)         | `/app/config.yaml`                              |\r\n| Volume(s)        | *None*                                          |\r\n| Environment Vars | `PKAPP_PORT` (sets port number, default=`9600`)\u003cbr /\u003e`PKAPP_LISTEN` (sets listen addr, default=`0.0.0.0`) |\r\n\r\n### Example\r\n\r\n```\r\ndocker run -d -p 9600:9600 -v /path/to/your/config.yaml:/app/config.yaml --name pricekeeper qjake/pricekeeper:latest\r\n```\r\n\r\n## Run with Docker Compose\r\n\r\nSample `docker-compose.yml`:\r\n\r\n```yaml\r\nversion: \"3.9\"\r\nservices:\r\n  pricekeeper:\r\n    container_name: pricekeeper\r\n    restart: unless-stopped\r\n    image: qjake/pricekeeper:latest\r\n    volumes:\r\n      - /path/to/your/config.yaml:/app/config.yaml\r\n    ports:\r\n      - \"9600:9600\"\r\n    # Optional, if you want to include a health check\r\n    healthcheck:\r\n      test: curl -s --fail http://127.0.0.1:9600/_health || exit 1\r\n      interval: 10s\r\n      retries: 3\r\n      start_period: 5s\r\n      timeout: 5s\r\n```\r\n\r\n## Configuration File Reference\r\n\r\n### Section: `storage`\r\n\r\n**Type:** object\r\n\r\n| Key | Type | Required | Value |\r\n| -- | -- | -- | -- |\r\n| `type` | string | ✅ | `azure` (Currently, only Azure Tables are supported.)\r\n| `account` | string | ✅  | Azure Storage account name (do not include \".table.core.windows.net\") |\r\n| `key` | string | ✅  | Azure Storage Accout access key (not a SAS, and not the connection string) |\r\n| `table` | string |  | Optionally, the prefix name to give to the tables that are created. See the tables documentation below for more info. |\r\n\r\n### Section: `scheduler`\r\n\r\n**Type:** object\r\n\r\n| Key | Type | Required | Value |\r\n| -- | -- | -- | -- |\r\n| `spreadtime` | int |  | Number of random seconds to add to a job to spread multiple jobs out that would otherwise run at the same time. (Sometimes referred to as \"jitter\".) Omit to disable random spread.\r\n\r\n### Section: `rules`\r\n\r\n**Type:** array\r\n\r\n| Key | Type | Required | Templatable | Variables | Value |\r\n| -- | -- | --| -- | -- | -- |\r\n| `name` | string | ✅ | | | A distinct name for this rule. Do not include any special (URL-unsafe) characters. |\r\n| `category` | string | ✅ | ✅ | | A category to file this rule under, for visually grouping rules. Rules with the exact same category name are displayed together. (e.g. \"Amazon\" or \"Google\") |\r\n| `url` | string | ✅ | ✅ | ✅ | The URL to fetch that contains the price. The price must be in the source/response body of the page (dynamic prices rendered via JS will not work or will require a different/creative solution). Doesn't have to be HTML - you can fetch a public API endpoint too.\r\n| `link` | string | | ✅ | ✅ | If set, will display a button on the UI that will open this link in a new tab. Useful for quickly navigating to the store page.\r\n| `hours` | string | ✅ | ✅ | | A cron-like expression for which hours to run the price fetch job. (e.g. `'*'` or `'9,12,15'` or `'*/3'`)\r\n| `mins` | string | | ✅ | | A cron-like expression for which minutes to run the price fetch job. Defaults to `'0'`.\r\n| `selector` | string \\| array[string] | ✅ (or `regex`) | ✅ | ✅ | One or more CSS selectors to look for a price value inside. The text value of all of the matched elements is taken as a single string, and a decimal value is extracted from the text. If multiple selectors are specified, they are executed from top to bottom until a price is found.\r\n| `regex` | string \\| array[string] | ✅ (or `selector`) | ✅ | ✅ | One or more regular expressions to look for a price value. **The first capture group should contain a price-like value.** (If you need other capture groups in your expression, make sure they are non-capture groups [`(?:...)`].) If multiple expressions are specified, they are executed from top to bottom until a price is found.\r\n| `divide` | bool | | ✅ | | `true` if the price is in a non-decimal format like `2000` but should be interpreted as `$20.00`. The value will be divided by 100.\r\n| `referer` | string | | ✅ |  ✅ | If set, will send an HTTP `Referer` header with this value.\r\n| `template` | string | | | | If set, will apply a template to this rule. See the template section below for more information.\r\n\r\n### Section: `cache` (optional)\r\n\r\n**Type:** object\r\n\r\nYou may want to track prices from a public API endpoint, or a page with multiple prices on it. Cache calls to a specific URL to improve performance.\r\n\r\nThe key is the URL and the value is the amount of seconds to cache that URL for.\r\n\r\nFor example:\r\n\r\n```yaml\r\ncache:\r\n  https://example.org/store/prices: 600 # 5 mins\r\n  https://example.com/product/listing: 120 # 2 mins\r\n```\r\n\r\n### Section: `templates` (optional)\r\n\r\n**Type:** object\r\n\r\nTemplates are used to apply common attributes to multiple rules. For example, the selector `'.a-price .a-offscreen'` can apply to most, if not all, Amazon listings. So instead of duplicating the rule many times, you can apply a template to it instead.\r\n\r\nDefine a template by giving it a name, as the key of a dictionary.\r\n\r\nThe properties inside the template definition will be copied to each rule that inherits from this template.\r\n\r\n#### Variables\r\n\r\nVariables can be defined on a rule, and applied to various properties on that rule (or on the parent template).\r\n\r\nFor example, if you have 10 rules for a fictional store such as `https://mystore.example.com/product/123`, you can put the URL into the parent template like so:\r\n\r\n```yaml\r\ntemplates:\r\n  ...\r\n  mystore:\r\n    url: https://mystore.example.com/product/{id}\r\n```\r\n\r\nAnd then, instead of duplicating each URL, simply define the variable for the product ID in the rule:\r\n\r\n```yaml\r\nrules:\r\n  - name: My Product\r\n    template: mystore\r\n    vars:\r\n      id: 123\r\n```\r\n\r\nYou can define as many variables as you want. Refer to the table above for which proeprties support variable replacement.\r\n\r\n#### Example\r\n\r\nThe original `config.yaml` example at the top of this Readme contains duplicated information. We can rewrite this example using templates to avoid duplication:\r\n\r\n```yaml\r\nstorage:\r\n  type: azure\r\n  account: mystorageaccountname\r\n  key: abcde............................67890==\r\n\r\nscheduler:\r\n  spreadtime: 30\r\n\r\nrules:\r\n  - name: Complete Guide to Docker for Beginners\r\n    template: amazon\r\n    vars:\r\n      productId: 'B08BW5Y73D'\r\n\r\n  - name: Learning Python 5th Edition\r\n    template: amazon\r\n    vars:\r\n      productId: '1449355730'\r\n\r\n  - name: Pixel 7 Pro\r\n    template: google\r\n    url: https://store.google.com/product/pixel_7_pro\r\n\r\n  - name: Pixel 7 Pro Case\r\n    template: google\r\n    url: https://store.google.com/product/casemate_tough_clear_cases\r\n\r\ntemplates:\r\n  amazon:\r\n    category: Amazon\r\n    hours: '*'\r\n    selector: '.a-price .a-offscreen'\r\n    url: https://www.amazon.com/dp/{productId}/\r\n    link: https://www.amazon.com/dp/{productId}/\r\n\r\n  google:\r\n    category: Google\r\n    hours: '0,12'\r\n    regex:\r\n      - meta itemprop=\"lowPrice\" content=\"(.+?)\"\r\n      - meta itemprop=\"price\" content=\"(.+?)\"\r\n```\r\n\r\n## Tables\r\n\r\nPriceKeeper creates the following tables:\r\n\r\n* `prices` - Used to store historical pricing data\r\n* `current` - Used to store current pricing data (only for quick retrieval of summary data)\r\n* `sparklines` - Used to store the generated sparkline images of the price history for the past 48 hours (only one image is kept per rule)\r\n\r\nIf the optional `config.storage.table` key is present, the tables will be prefixed by this value separated by an underscore. For example, if the configuration is:\r\n\r\n```yaml\r\nconfig:\r\n  storage:\r\n    ...\r\n    table: myvalue\r\n```\r\n\r\nThen the prices table would be created in Azure as `myvalue_prices` instead of the default `prices`.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fqjake%2Fpricekeeper","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fqjake%2Fpricekeeper","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fqjake%2Fpricekeeper/lists"}